GP-1230: Add Taint Analysis prototype and emulator framework support

This commit is contained in:
Dan 2022-08-22 14:15:14 -04:00
parent 4bfd8d1112
commit 51a1933ab3
205 changed files with 11214 additions and 3714 deletions

View file

@ -33,7 +33,7 @@ dependencies {
helpPath project(path: ':Base', configuration: 'helpPath') helpPath project(path: ':Base', configuration: 'helpPath')
helpPath project(path: ':ProgramDiff', 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-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts') testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts') testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts')

View file

@ -2,7 +2,9 @@ AutoReadMemorySpec
DebuggerBot DebuggerBot
DebuggerMappingOpinion DebuggerMappingOpinion
DebuggerModelFactory DebuggerModelFactory
DebuggerPcodeEmulatorFactory
DebuggerPlatformOpinion DebuggerPlatformOpinion
DebuggerProgramLaunchOpinion DebuggerProgramLaunchOpinion
DebuggerRegisterColumnFactory
DisassemblyInject DisassemblyInject
LocationTrackingSpec LocationTrackingSpec

View file

@ -28,7 +28,7 @@ import java.util.List;
import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers; 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.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript; 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 * library. This emulator will still know how to integrate with the UI, reading through to
* open programs and writing state back into the trace. * 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 @Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() { protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
@ -169,7 +169,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
thread.stepInstruction(); thread.stepInstruction();
snapshot = snapshot =
time.createSnapshot("Stepped to " + thread.getCounter()); 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!"); printerr("We should not have completed 10 steps!");
} }

View file

@ -29,16 +29,17 @@ import ghidra.program.model.pcode.Varnode;
* A userop library for the emulator * A userop library for the emulator
* *
* <p> * <p>
* If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These * If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries
* libraries allow you to implement userop, including those declared by the language. Without these, * allow you to implement userops, including those declared by the language. Without these, the
* the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also * emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define
* define new userops, which can be invoked from Sleigh code injected into the emulator. * new userops, which can be invoked from Sleigh code injected into the emulator.
* *
* <p> * <p>
* These libraries can have both Java-callback and p-code implementations of userops. If only using * 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 * 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 * 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> * <p>
* Methods in this class (not including those in its nested classes) are implemented as Java * 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 * @return the length of the string in bytes
*/ */
@PcodeUserop @PcodeUserop
public byte[] print_utf8(@OpState PcodeExecutorStatePiece<byte[], byte[]> state, public byte[] print_utf8(@OpState PcodeExecutorState<byte[]> state, byte[] start) {
byte[] start) {
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian()); long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
long end = offset; long end = offset;
while (state.getVar(space, end, 1, true)[0] != 0) { while (state.getVar(space, end, 1, true)[0] != 0) {

View file

@ -23,6 +23,7 @@ import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary; import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuSyscallLibrary; import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.struct.StructuredSleigh; import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace; 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 * 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 * {@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 * 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> * <p>
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in * 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> * <p>
* For demonstration, this will implement one from scratch for no particular operating system, but * 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[]> { public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
private final static Charset UTF8 = Charset.forName("utf8"); 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 * 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 * program is required. Use the system call analyzer on your program to populate this space. The
* compiler spec are also used to derive (what it can of) the system call ABI. Notably, it * program and its compiler spec are also used to derive (what it can of) the system call ABI.
* applies the calling convention of the functions placed in syscall overlay. Those parts which * Notably, it applies the calling convention of the functions placed in syscall overlay. Those
* cannot (yet) be derived from the program are instead implemented as abstract methods of this * parts which cannot (yet) be derived from the program are instead implemented as abstract
* class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and * methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}. * {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
* *
* @param machine the emulator * @param machine the emulator
@ -151,7 +153,7 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
* <p> * <p>
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the * 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 * userop name should be prefixed with the platform name, to avoid naming collisions among
* userops. * composed libraries.
* *
* <p> * <p>
* For demonstration, we will export this as a system call, though that is not required for * 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. * copy of the arithmetic as a field at library construction time.
*/ */
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic(); PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
long strLong = arithmetic.toConcrete(str).longValue(); long strLong = arithmetic.toLong(str, Purpose.LOAD);
long endLong = arithmetic.toConcrete(end).longValue(); long endLong = arithmetic.toLong(end, Purpose.OTHER);
byte[] stringBytes = byte[] stringBytes =
machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true); machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true);
@ -185,12 +187,17 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
// Second, a Structured Sleigh example // Second, a Structured Sleigh example
/** /**
* The nested class for syscall implemented using StructuredSleigh. Note that no matter the * 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 it * implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
* public so that the annotation processor can access the methods. Alternatively, we could * class public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()}. * override {@link #getMethodLookup()} to provide the processor private access.
*/ */
public class DemoStructuredPart extends StructuredPart { 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 *")); UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
/** /**

View file

@ -21,8 +21,6 @@
//@menupath //@menupath
//@toolbar //@toolbar
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
@ -88,7 +86,7 @@ public class StandAloneEmuExampleScript extends GhidraScript {
*/ */
Address entry = dyn.getAddress(0x00400000); Address entry = dyn.getAddress(0x00400000);
Assembler asm = Assemblers.getAssembler(language); Assembler asm = Assemblers.getAssembler(language);
CodeBuffer buffer = new CodeBuffer(asm, entry); AssemblyBuffer buffer = new AssemblyBuffer(asm, entry);
buffer.assemble("MOV RCX, 0xdeadbeef"); buffer.assemble("MOV RCX, 0xdeadbeef");
Address injectHere = buffer.getNext(); Address injectHere = buffer.getNext();
buffer.assemble("MOV RAX, 1"); buffer.assemble("MOV RAX, 1");
@ -150,30 +148,4 @@ public class StandAloneEmuExampleScript extends GhidraScript {
.evaluate(thread.getExecutor()), .evaluate(thread.getExecutor()),
8, language.isBigEndian())); 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();
}
}
} }

View file

@ -40,5 +40,11 @@
current address and whose registers are initialized to the register context at the current 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 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> 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> </BODY>
</HTML> </HTML>

View file

@ -28,18 +28,19 @@ import ghidra.util.table.GhidraTableFilterPanel;
public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogComponentProvider { public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogComponentProvider {
protected final EnumeratedColumnTableModel<R> tableModel = createTableModel(); protected final EnumeratedColumnTableModel<R> tableModel;
protected GTable table; protected GTable table;
protected GhidraTableFilterPanel<R> filterPanel; protected GhidraTableFilterPanel<R> filterPanel;
private Collection<R> adjusted; private Collection<R> adjusted;
protected AbstractDebuggerMapProposalDialog(String title) { protected AbstractDebuggerMapProposalDialog(PluginTool tool, String title) {
super(title, true, true, true, false); super(title, true, true, true, false);
tableModel = createTableModel(tool);
populateComponents(); populateComponents();
} }
protected abstract EnumeratedColumnTableModel<R> createTableModel(); protected abstract EnumeratedColumnTableModel<R> createTableModel(PluginTool tool);
protected void populateComponents() { protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());

View file

@ -143,16 +143,17 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
} }
} }
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel = final EnumeratedColumnTableModel<MemoryBlockRow> tableModel;
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
GTable table; GTable table;
GhidraTableFilterPanel<MemoryBlockRow> filterPanel; GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
private Entry<Program, MemoryBlock> chosen; private Entry<Program, MemoryBlock> chosen;
public DebuggerBlockChooserDialog() { public DebuggerBlockChooserDialog(PluginTool tool) {
super("Memory Blocks", true, true, true, false); super("Memory Blocks", true, true, true, false);
tableModel =
new DefaultEnumeratedColumnTableModel<>(tool, "Blocks", MemoryBlockTableColumns.class);
populateComponents(); populateComponents();
} }

View file

@ -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 { abstract class AbstractQuickLaunchAction extends DockingAction {
public static final String NAME = "Quick Launch"; public static final String NAME = "Quick Launch";
public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon? public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon?

View file

@ -93,7 +93,7 @@ public abstract class DebuggerGoToTrait {
} }
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) { 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); CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> { return result.thenApply(offset -> {
Address address = space.getAddress( Address address = space.getAddress(

View file

@ -134,7 +134,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> { LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> {
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) { public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb, super(provider.getTool(), "Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
lb -> new LogicalBreakpointRow(provider, lb)); lb -> new LogicalBreakpointRow(provider, lb));
} }
@ -212,8 +212,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> { BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) { public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) {
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey, super(provider.getTool(), "Locations", BreakpointLocationTableColumns.class,
loc -> new BreakpointLocationRow(provider, loc)); TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc));
} }
@Override @Override

View file

@ -45,8 +45,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel; import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.*; import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
@ -205,8 +204,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< // protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
LogTableColumns, ActionContext, LogRow, LogRow> { LogTableColumns, ActionContext, LogRow, LogRow> {
public LogTableModel() { public LogTableModel(PluginTool tool) {
super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r); super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
} }
@Override @Override
@ -286,7 +285,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
protected final Map<String, Map<String, DockingActionIf>> actionsByOwnerThenName = protected final Map<String, Map<String, DockingActionIf>> actionsByOwnerThenName =
new LinkedHashMap<>(); new LinkedHashMap<>();
protected final LogTableModel logTableModel = new LogTableModel(); protected final LogTableModel logTableModel;
protected GhidraTable logTable; protected GhidraTable logTable;
private GhidraTableFilterPanel<LogRow> logFilterPanel; private GhidraTableFilterPanel<LogRow> logFilterPanel;
@ -301,6 +300,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
logTableModel = new LogTableModel(tool);
tool.addPopupActionProvider(this); tool.addPopupActionProvider(this);
setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE); setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE);

View file

@ -55,7 +55,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
return ctx.hasSelection() ? ctx.getSelection() : null; return ctx.hasSelection() ? ctx.getSelection() : null;
} }
protected DebuggerCopyIntoProgramDialog copyDialog = new DebuggerCopyIntoProgramDialog(); protected DebuggerCopyIntoProgramDialog copyDialog;
protected DockingAction actionExportView; protected DockingAction actionExportView;
protected DockingAction actionCopyIntoCurrentProgram; protected DockingAction actionCopyIntoCurrentProgram;
@ -70,6 +70,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
public DebuggerCopyActionsPlugin(PluginTool tool) { public DebuggerCopyActionsPlugin(PluginTool tool) {
super(tool); super(tool);
copyDialog = new DebuggerCopyIntoProgramDialog(tool);
createActions(); createActions();
} }

View file

@ -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.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -200,8 +201,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
protected static class RangeTableModel protected static class RangeTableModel
extends DefaultEnumeratedColumnTableModel<RangeTableColumns, RangeEntry> { extends DefaultEnumeratedColumnTableModel<RangeTableColumns, RangeEntry> {
public RangeTableModel() { public RangeTableModel(PluginTool tool) {
super("Ranges", RangeTableColumns.class); super(tool, "Ranges", RangeTableColumns.class);
} }
@Override @Override
@ -302,15 +303,16 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
protected JCheckBox cbUseOverlays; protected JCheckBox cbUseOverlays;
protected DebuggerCopyPlan plan = new DebuggerCopyPlan(); protected DebuggerCopyPlan plan = new DebuggerCopyPlan();
protected final RangeTableModel tableModel = new RangeTableModel(); protected final RangeTableModel tableModel;
protected GTable table; protected GTable table;
protected GhidraTableFilterPanel<RangeEntry> filterPanel; protected GhidraTableFilterPanel<RangeEntry> filterPanel;
protected JButton resetButton; protected JButton resetButton;
public DebuggerCopyIntoProgramDialog() { public DebuggerCopyIntoProgramDialog(PluginTool tool) {
super("Copy Into Program", true, true, true, true); super("Copy Into Program", true, true, true, true);
tableModel = new RangeTableModel(tool);
populateComponents(); populateComponents();
} }

View file

@ -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;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
@ -101,8 +102,8 @@ public class DebuggerRegionMapProposalDialog
protected static class RegionMapPropsalTableModel extends protected static class RegionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<RegionMapTableColumns, RegionMapEntry> { DefaultEnumeratedColumnTableModel<RegionMapTableColumns, RegionMapEntry> {
public RegionMapPropsalTableModel() { public RegionMapPropsalTableModel(PluginTool tool) {
super("Region Map", RegionMapTableColumns.class); super(tool, "Region Map", RegionMapTableColumns.class);
} }
@Override @Override
@ -114,13 +115,13 @@ public class DebuggerRegionMapProposalDialog
private final DebuggerRegionsProvider provider; private final DebuggerRegionsProvider provider;
public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) { public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) {
super(MapRegionsAction.NAME); super(provider.getTool(), MapRegionsAction.NAME);
this.provider = provider; this.provider = provider;
} }
@Override @Override
protected RegionMapPropsalTableModel createTableModel() { protected RegionMapPropsalTableModel createTableModel(PluginTool tool) {
return new RegionMapPropsalTableModel(); return new RegionMapPropsalTableModel(tool);
} }
@Override @Override

View file

@ -44,8 +44,7 @@ import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTab
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -124,8 +123,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< // extends DebouncedRowWrappedEnumeratedColumnTableModel< //
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> { RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
public RegionTableModel() { public RegionTableModel(PluginTool tool) {
super("Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey, super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new); RegionRow::new);
} }
} }
@ -233,7 +232,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
private final RegionsListener regionsListener = new RegionsListener(); private final RegionsListener regionsListener = new RegionsListener();
protected final RegionTableModel regionTableModel = new RegionTableModel(); protected final RegionTableModel regionTableModel;
protected GhidraTable regionTable; protected GhidraTable regionTable;
private GhidraTableFilterPanel<RegionRow> regionFilterPanel; private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
@ -260,6 +259,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
DebuggerRegionActionContext.class); DebuggerRegionActionContext.class);
this.plugin = plugin; this.plugin = plugin;
regionTableModel = new RegionTableModel(tool);
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS); setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS); setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
setWindowMenuGroup(DebuggerPluginPackage.NAME); setWindowMenuGroup(DebuggerPluginPackage.NAME);
@ -268,7 +269,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
blockChooserDialog = new DebuggerBlockChooserDialog(); blockChooserDialog = new DebuggerBlockChooserDialog(tool);
regionProposalDialog = new DebuggerRegionMapProposalDialog(this); regionProposalDialog = new DebuggerRegionMapProposalDialog(this);
setDefaultWindowPosition(WindowPosition.BOTTOM); setDefaultWindowPosition(WindowPosition.BOTTOM);

View file

@ -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;
}
}

View file

@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry; import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Swing; import ghidra.util.Swing;
@ -100,8 +101,8 @@ public class DebuggerModuleMapProposalDialog
protected static class ModuleMapPropsalTableModel extends protected static class ModuleMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<ModuleMapTableColumns, ModuleMapEntry> { DefaultEnumeratedColumnTableModel<ModuleMapTableColumns, ModuleMapEntry> {
public ModuleMapPropsalTableModel() { public ModuleMapPropsalTableModel(PluginTool tool) {
super("Module Map", ModuleMapTableColumns.class); super(tool, "Module Map", ModuleMapTableColumns.class);
} }
@Override @Override
@ -113,13 +114,13 @@ public class DebuggerModuleMapProposalDialog
private final DebuggerModulesProvider provider; private final DebuggerModulesProvider provider;
protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) { protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) {
super(MapModulesAction.NAME); super(provider.getTool(), MapModulesAction.NAME);
this.provider = provider; this.provider = provider;
} }
@Override @Override
protected ModuleMapPropsalTableModel createTableModel() { protected ModuleMapPropsalTableModel createTableModel(PluginTool tool) {
return new ModuleMapPropsalTableModel(); return new ModuleMapPropsalTableModel(tool);
} }
@Override @Override

View file

@ -53,8 +53,7 @@ import ghidra.async.TypeSpec;
import ghidra.framework.main.AppInfo; import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -207,8 +206,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< // extends DebouncedRowWrappedEnumeratedColumnTableModel< //
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> { ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
public ModuleTableModel() { public ModuleTableModel(PluginTool tool) {
super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new); super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
ModuleRow::new);
} }
@Override @Override
@ -221,8 +221,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< // extends DebouncedRowWrappedEnumeratedColumnTableModel< //
SectionTableColumns, ObjectKey, SectionRow, TraceSection> { SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
public SectionTableModel() { public SectionTableModel(PluginTool tool) {
super("Sections", SectionTableColumns.class, TraceSection::getObjectKey, super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
SectionRow::new); SectionRow::new);
} }
@ -555,11 +555,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
private final RecordersChangedListener recordersChangedListener = private final RecordersChangedListener recordersChangedListener =
new RecordersChangedListener(); new RecordersChangedListener();
protected final ModuleTableModel moduleTableModel = new ModuleTableModel(); protected final ModuleTableModel moduleTableModel;
protected GhidraTable moduleTable; protected GhidraTable moduleTable;
private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel; private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
protected final SectionTableModel sectionTableModel = new SectionTableModel(); protected final SectionTableModel sectionTableModel;
protected GhidraTable sectionTable; protected GhidraTable sectionTable;
protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel; protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules = private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
@ -599,6 +599,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null);
this.plugin = plugin; this.plugin = plugin;
moduleTableModel = new ModuleTableModel(tool);
sectionTableModel = new SectionTableModel(tool);
setIcon(DebuggerResources.ICON_PROVIDER_MODULES); setIcon(DebuggerResources.ICON_PROVIDER_MODULES);
setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES); setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES);
setWindowMenuGroup(DebuggerPluginPackage.NAME); setWindowMenuGroup(DebuggerPluginPackage.NAME);
@ -607,7 +610,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
blockChooserDialog = new DebuggerBlockChooserDialog(); blockChooserDialog = new DebuggerBlockChooserDialog(tool);
moduleProposalDialog = new DebuggerModuleMapProposalDialog(this); moduleProposalDialog = new DebuggerModuleMapProposalDialog(this);
sectionProposalDialog = new DebuggerSectionMapProposalDialog(this); sectionProposalDialog = new DebuggerSectionMapProposalDialog(this);

View file

@ -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;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction;
import ghidra.app.services.SectionMapProposal.SectionMapEntry; import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
@ -102,8 +103,8 @@ public class DebuggerSectionMapProposalDialog
protected static class SectionMapPropsalTableModel extends protected static class SectionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<SectionMapTableColumns, SectionMapEntry> { DefaultEnumeratedColumnTableModel<SectionMapTableColumns, SectionMapEntry> {
public SectionMapPropsalTableModel() { public SectionMapPropsalTableModel(PluginTool tool) {
super("Section Map", SectionMapTableColumns.class); super(tool, "Section Map", SectionMapTableColumns.class);
} }
@Override @Override
@ -115,13 +116,13 @@ public class DebuggerSectionMapProposalDialog
private final DebuggerModulesProvider provider; private final DebuggerModulesProvider provider;
public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) { public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) {
super(MapSectionsAction.NAME); super(provider.getTool(), MapSectionsAction.NAME);
this.provider = provider; this.provider = provider;
} }
@Override @Override
protected SectionMapPropsalTableModel createTableModel() { protected SectionMapPropsalTableModel createTableModel(PluginTool tool) {
return new SectionMapPropsalTableModel(); return new SectionMapPropsalTableModel(tool);
} }
@Override @Override

View file

@ -42,8 +42,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel; import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -103,9 +102,9 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
extends DebouncedRowWrappedEnumeratedColumnTableModel< // extends DebouncedRowWrappedEnumeratedColumnTableModel< //
StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> { StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> {
public MappingTableModel() { public MappingTableModel(PluginTool tool) {
super("Mappings", StaticMappingTableColumns.class, TraceStaticMapping::getObjectKey, super(tool, "Mappings", StaticMappingTableColumns.class,
StaticMappingRow::new); TraceStaticMapping::getObjectKey, StaticMappingRow::new);
} }
} }
@ -149,7 +148,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay(); private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay();
protected final MappingTableModel mappingTableModel = new MappingTableModel(); protected final MappingTableModel mappingTableModel;
private JPanel mainPanel = new JPanel(new BorderLayout()); private JPanel mainPanel = new JPanel(new BorderLayout());
protected GTable mappingTable; protected GTable mappingTable;
@ -165,6 +164,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null);
this.plugin = plugin; this.plugin = plugin;
mappingTableModel = new MappingTableModel(tool);
this.addMappingDialog = new DebuggerAddMappingDialog(); this.addMappingDialog = new DebuggerAddMappingDialog();
this.autoWiring = AutoService.wireServicesConsumed(plugin, this); this.autoWiring = AutoService.wireServicesConsumed(plugin, this);

View file

@ -647,7 +647,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
TargetObject targetObject = container.getTargetObject(); TargetObject targetObject = container.getTargetObject();
String name = targetObject.getName(); String name = targetObject.getName();
DefaultEnumeratedColumnTableModel<?, ObjectAttributeRow> model = DefaultEnumeratedColumnTableModel<?, ObjectAttributeRow> model =
new DefaultEnumeratedColumnTableModel<>(name, ObjectAttributeColumn.class); new DefaultEnumeratedColumnTableModel<>(tool, name, ObjectAttributeColumn.class);
Map<String, Object> map = container.getAttributeMap(); Map<String, Object> map = container.getAttributeMap();
List<ObjectAttributeRow> list = new ArrayList<>(); List<ObjectAttributeRow> list = new ArrayList<>();
for (Object val : map.values()) { for (Object val : map.values()) {

View file

@ -64,9 +64,7 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
protected RefreshAction actionRefresh; protected RefreshAction actionRefresh;
protected JButton attachButton; protected JButton attachButton;
private final RowObjectTableModel<TargetAttachable> processes = private final RowObjectTableModel<TargetAttachable> processes;
new DefaultEnumeratedColumnTableModel<>("Attachables",
AttachableProcessesTableColumns.class);
protected TargetAttacher attacher; protected TargetAttacher attacher;
private GTable processTable; private GTable processTable;
@ -74,6 +72,8 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
super(AbstractAttachAction.NAME, true, true, true, false); super(AbstractAttachAction.NAME, true, true, true, false);
this.provider = provider; this.provider = provider;
this.plugin = provider.getPlugin(); this.plugin = provider.getPlugin();
processes = new DefaultEnumeratedColumnTableModel<>(plugin.getTool(), "Attachables",
AttachableProcessesTableColumns.class);
populateComponents(); populateComponents();
createActions(); createActions();

View file

@ -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 List<R> modelData = new ArrayList<>();
private final String name; private final String name;
private C[] cols; private C[] cols;

View file

@ -38,7 +38,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.pcode.UniqueRow.RefType; 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.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
@ -49,12 +49,10 @@ import ghidra.base.widgets.table.DataTypeTableCellEditor;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.*; import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeFrame;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@ -143,8 +141,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
protected static class PcodeTableModel protected static class PcodeTableModel
extends DefaultEnumeratedColumnTableModel<PcodeTableColumns, PcodeRow> { extends DefaultEnumeratedColumnTableModel<PcodeTableColumns, PcodeRow> {
public PcodeTableModel() { public PcodeTableModel(PluginTool tool) {
super("p-code", PcodeTableColumns.class); super(tool, "p-code", PcodeTableColumns.class);
} }
@Override @Override
@ -208,8 +206,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
protected static class UniqueTableModel protected static class UniqueTableModel
extends DefaultEnumeratedColumnTableModel<UniqueTableColumns, UniqueRow> { extends DefaultEnumeratedColumnTableModel<UniqueTableColumns, UniqueRow> {
public UniqueTableModel() { public UniqueTableModel(PluginTool tool) {
super("Unique", UniqueTableColumns.class); super(tool, "Unique", UniqueTableColumns.class);
} }
@Override @Override
@ -575,12 +573,12 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final UniqueTableModel uniqueTableModel;
GhidraTable uniqueTable; GhidraTable uniqueTable;
UniqueTableModel uniqueTableModel = new UniqueTableModel();
GhidraTableFilterPanel<UniqueRow> uniqueFilterPanel; GhidraTableFilterPanel<UniqueRow> uniqueFilterPanel;
final PcodeTableModel pcodeTableModel;
GhidraTable pcodeTable; GhidraTable pcodeTable;
PcodeTableModel pcodeTableModel = new PcodeTableModel();
JLabel instructionLabel; JLabel instructionLabel;
// No filter panel on p-code // No filter panel on p-code
PcodeCellRenderer codeColRenderer; PcodeCellRenderer codeColRenderer;
@ -592,6 +590,9 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null);
this.plugin = plugin; this.plugin = plugin;
uniqueTableModel = new UniqueTableModel(tool);
pcodeTableModel = new PcodeTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this); this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
@ -877,9 +878,10 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
pcodeTableModel.add(row); 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); populatePcode(frame);
populateUnique(frame, state); populateUnique(frame, state, arithmetic);
} }
protected int computeCodeColWidth(List<PcodeRow> rows) { protected int computeCodeColWidth(List<PcodeRow> rows) {
@ -916,7 +918,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
pcodeTable.scrollToSelectedRow(); 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(); Language language = current.getTrace().getBaseLanguage();
// NOTE: They may overlap. I don't think I care. // NOTE: They may overlap. I don't think I care.
Set<Varnode> uniques = new TreeSet<>(UNIQUE_COMPARATOR); Set<Varnode> uniques = new TreeSet<>(UNIQUE_COMPARATOR);
@ -936,7 +939,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
// TODO: Permit modification of unique variables // TODO: Permit modification of unique variables
List<UniqueRow> toAdd = List<UniqueRow> toAdd =
uniques.stream() uniques.stream()
.map(u -> new UniqueRow(this, language, state, u)) .map(u -> new UniqueRow(this, language, state, arithmetic, u))
.collect(Collectors.toList()); .collect(Collectors.toList());
uniqueTableModel.addAll(toAdd); uniqueTableModel.addAll(toAdd);
} }
@ -971,7 +974,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
populateSingleton(EnumPcodeRow.DECODE); populateSingleton(EnumPcodeRow.DECODE);
return; return;
} }
DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time); DebuggerPcodeMachine<?> emu = emulationService.getCachedEmulator(trace, time);
if (emu != null) { if (emu != null) {
clear(); clear();
doLoadPcodeFrameFromEmulator(emu); doLoadPcodeFrameFromEmulator(emu);
@ -986,8 +989,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
}, SwingExecutorService.LATER); }, SwingExecutorService.LATER);
} }
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) { protected <T> void doLoadPcodeFrameFromEmulator(DebuggerPcodeMachine<T> emu) {
PcodeThread<byte[]> thread = emu.getThread(current.getThread().getPath(), false); PcodeThread<T> thread = emu.getThread(current.getThread().getPath(), false);
if (thread == null) { if (thread == null) {
/** /**
* Happens when focus is on a thread not stepped in the schedule. Stepping it would * 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); populateSingleton(EnumPcodeRow.DECODE);
return; return;
} }
populateFromFrame(frame, thread.getState()); populateFromFrame(frame, thread.getState(), thread.getArithmetic());
} }
@AutoServiceConsumed @AutoServiceConsumed

View file

@ -19,8 +19,9 @@ import java.math.BigInteger;
import java.util.stream.Stream; import java.util.stream.Stream;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@ -39,36 +40,54 @@ public class UniqueRow {
if (isWrite) { if (isWrite) {
return READ_WRITE; return READ_WRITE;
} }
else { return READ;
return READ;
}
} }
else { if (isWrite) {
if (isWrite) { return WRITE;
return WRITE;
}
else {
return NONE;
}
} }
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 DebuggerPcodeStepperProvider provider;
protected final Language language; protected final Language language;
protected final PcodeExecutorState<byte[]> state; protected final ConcretizedState<?> state;
protected final Varnode vn; protected final Varnode vn;
protected DataType dataType; protected DataType dataType;
public UniqueRow(DebuggerPcodeStepperProvider provider, Language language, public <T> UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
PcodeExecutorState<byte[]> state, Varnode vn) { PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic, Varnode vn) {
if (!vn.isUnique()) { if (!vn.isUnique()) {
throw new AssertionError("Only uniques allowed in unique table"); throw new AssertionError("Only uniques allowed in unique table");
} }
this.provider = provider; this.provider = provider;
this.language = language; this.language = language;
this.state = state; this.state = new ConcretizedState<>(state, arithmetic);
this.vn = vn; this.vn = vn;
} }
@ -105,9 +124,26 @@ public class UniqueRow {
return String.format("$U%x:%d", vn.getOffset(), vn.getSize()); 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() { public String getBytes() {
// TODO: Could keep value cached? // 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) { if (bytes == null) {
return "??"; return "??";
} }
@ -117,9 +153,18 @@ public class UniqueRow {
return NumericUtilities.convertBytesToString(bytes, " "); 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() { public BigInteger getValue() {
byte[] bytes = state.getVar(vn); try {
return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false); return state.getValue(vn);
}
catch (UnsupportedOperationException e) {
return null;
}
} }
public DataType getDataType() { public DataType getDataType() {
@ -135,7 +180,7 @@ public class UniqueRow {
if (dataType == null) { if (dataType == null) {
return ""; return "";
} }
byte[] bytes = state.getVar(vn); byte[] bytes = state.getBytes(vn);
if (bytes == null) { if (bytes == null) {
return "??"; return "??";
} }

View file

@ -203,8 +203,7 @@ public class DebuggerPlatformPlugin extends Plugin {
private final ChangeListener classChangeListener = evt -> this.classesChanged(); private final ChangeListener classChangeListener = evt -> this.classesChanged();
protected final DebuggerSelectPlatformOfferDialog offerDialog = protected final DebuggerSelectPlatformOfferDialog offerDialog;
new DebuggerSelectPlatformOfferDialog();
final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>(); final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>();
DockingAction actionMore; DockingAction actionMore;
@ -212,6 +211,7 @@ public class DebuggerPlatformPlugin extends Plugin {
public DebuggerPlatformPlugin(PluginTool tool) { public DebuggerPlatformPlugin(PluginTool tool) {
super(tool); super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
offerDialog = new DebuggerSelectPlatformOfferDialog(tool);
ClassSearcher.addChangeListener(classChangeListener); ClassSearcher.addChangeListener(classChangeListener);

View file

@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
@ -134,8 +135,8 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
public static class OfferTableModel public static class OfferTableModel
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> { extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> {
public OfferTableModel() { public OfferTableModel(PluginTool tool) {
super("Offers", OfferTableColumns.class); super(tool, "Offers", OfferTableColumns.class);
} }
@Override @Override
@ -146,14 +147,13 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
} }
public static class OfferPanel extends JPanel { public static class OfferPanel extends JPanel {
private final OfferTableModel offerTableModel = new OfferTableModel(); private final OfferTableModel offerTableModel;
private final GhidraTable offerTable = new GhidraTable(offerTableModel); private final GhidraTable offerTable;
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel = private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel;
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
private final JLabel descLabel = new JLabel(); private final JLabel descLabel = new JLabel();
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers"); private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
private final JScrollPane scrollPane = new JScrollPane(offerTable) { private final JScrollPane scrollPane = new JScrollPane() {
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize(); Dimension pref = super.getPreferredSize();
@ -178,7 +178,12 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
private LanguageID preferredLangID; private LanguageID preferredLangID;
private CompilerSpecID preferredCsID; 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()); JPanel descPanel = new JPanel(new BorderLayout());
descPanel.setBorder(BorderFactory.createTitledBorder("Description")); descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
descPanel.add(descLabel, BorderLayout.CENTER); 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; private boolean isCancelled = false;
protected DebuggerSelectPlatformOfferDialog() { protected DebuggerSelectPlatformOfferDialog(PluginTool tool) {
super(DebuggerResources.NAME_CHOOSE_PLATFORM, true, false, true, false); super(DebuggerResources.NAME_CHOOSE_PLATFORM, true, false, true, false);
offerPanel = new OfferPanel(tool);
populateComponents(); populateComponents();
} }

View file

@ -32,6 +32,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
@ -105,8 +106,8 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
protected static class AvailableRegistersTableModel extends protected static class AvailableRegistersTableModel extends
DefaultEnumeratedColumnTableModel<AvailableRegisterTableColumns, AvailableRegisterRow> { DefaultEnumeratedColumnTableModel<AvailableRegisterTableColumns, AvailableRegisterRow> {
public AvailableRegistersTableModel() { public AvailableRegistersTableModel(PluginTool tool) {
super("Available Registers", AvailableRegisterTableColumns.class); super(tool, "Available Registers", AvailableRegisterTableColumns.class);
} }
@Override @Override
@ -119,8 +120,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
private Language language; private Language language;
/* testing */ final AvailableRegistersTableModel availableTableModel = /* testing */ final AvailableRegistersTableModel availableTableModel;
new AvailableRegistersTableModel();
private final Map<Register, AvailableRegisterRow> regMap = new HashMap<>(); private final Map<Register, AvailableRegisterRow> regMap = new HashMap<>();
private GTable availableTable; private GTable availableTable;
@ -135,6 +135,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
super(DebuggerResources.SelectRegistersAction.NAME, true, true, true, false); super(DebuggerResources.SelectRegistersAction.NAME, true, true, true, false);
this.provider = provider; this.provider = provider;
availableTableModel = new AvailableRegistersTableModel(provider.getTool());
populateComponents(); populateComponents();
} }

View file

@ -13,4 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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();
}

View file

@ -77,6 +77,7 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.*; import ghidra.trace.util.*;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.data.DataTypeParser.AllowedDataTypes; import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -201,14 +202,24 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
protected static class RegistersTableModel protected static class RegistersTableModel
extends DefaultEnumeratedColumnTableModel<RegisterTableColumns, RegisterRow> { extends DefaultEnumeratedColumnTableModel<RegisterTableColumns, RegisterRow> {
public RegistersTableModel() { public RegistersTableModel(PluginTool tool) {
super("Registers", RegisterTableColumns.class); super(tool, "Registers", RegisterTableColumns.class);
} }
@Override @Override
public List<RegisterTableColumns> defaultSortOrder() { public List<RegisterTableColumns> defaultSortOrder() {
return List.of(RegisterTableColumns.FAV, RegisterTableColumns.NUMBER); 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) { protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
@ -472,8 +483,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
private JPanel mainPanel = new JPanel(new BorderLayout()); private JPanel mainPanel = new JPanel(new BorderLayout());
final RegistersTableModel regsTableModel;
GhidraTable regsTable; GhidraTable regsTable;
RegistersTableModel regsTableModel = new RegistersTableModel();
GhidraTableFilterPanel<RegisterRow> regsFilterPanel; GhidraTableFilterPanel<RegisterRow> regsFilterPanel;
Map<Register, RegisterRow> regMap = new HashMap<>(); Map<Register, RegisterRow> regMap = new HashMap<>();
@ -495,6 +506,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
boolean isClone) { boolean isClone) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
regsTableModel = new RegistersTableModel(tool);
this.selectionByCSpec = selectionByCSpec; this.selectionByCSpec = selectionByCSpec;
this.favoritesByCSpec = favoritesByCSpec; this.favoritesByCSpec = favoritesByCSpec;
this.isClone = isClone; this.isClone = isClone;
@ -1387,4 +1401,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES)); DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES));
} }
} }
public DebuggerCoordinates getCurrent() {
return current;
}
} }

View file

@ -18,30 +18,49 @@ package ghidra.app.plugin.core.debug.gui.register;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Objects; 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.data.DataType;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.trace.model.Trace;
import ghidra.util.Msg; import ghidra.util.Msg;
/**
* A row displayed in the registers table of the Debugger
*/
public class RegisterRow { public class RegisterRow {
private final DebuggerRegistersProvider provider; private final DebuggerRegistersProvider provider;
private boolean favorite; private boolean favorite;
private final int number; private final int number;
private final Register register; private final Register register;
public RegisterRow(DebuggerRegistersProvider provider, int number, Register register) { protected RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
this.provider = provider; this.provider = provider;
this.number = number; this.number = number;
this.register = Objects.requireNonNull(register); this.register = Objects.requireNonNull(register);
this.favorite = provider.isFavorite(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) { public void setFavorite(boolean favorite) {
this.favorite = favorite; this.favorite = favorite;
provider.setFavorite(register, favorite); provider.setFavorite(register, favorite);
} }
/**
* Check if this register is one of the user's favorites
*
* @return true if favorite
*/
public boolean isFavorite() { public boolean isFavorite() {
return favorite; return favorite;
} }
@ -55,18 +74,42 @@ public class RegisterRow {
return number; return number;
} }
/**
* Get the register
*
* @return the register
*/
public Register getRegister() { public Register getRegister() {
return register; return register;
} }
/**
* Get the register's name
*
* @return the name
*/
public String getName() { public String getName() {
return register.getName(); return register.getName();
} }
/**
* Check if the register can be edited
*
* @return true if editable
*/
public boolean isValueEditable() { public boolean isValueEditable() {
return provider.canWriteRegister(register); 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) { public void setValue(BigInteger value) {
try { try {
provider.writeRegisterValue(register, value); 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 * TODO: Perhaps some caching for all these getters which rely on the DB, since they could be
* invoked on every repaint. * invoked on every repaint.
*
* @return the value
*/ */
public BigInteger getValue() { public BigInteger getValue() {
return provider.getRegisterValue(register); return provider.getRegisterValue(register);
@ -89,31 +137,78 @@ public class RegisterRow {
return provider.getRegisterData(register); 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) { public void setDataType(DataType dataType) {
provider.writeRegisterDataType(register, dataType); provider.writeRegisterDataType(register, dataType);
} }
/**
* Get the data type of the register
*
* @return the data type
*/
public DataType getDataType() { public DataType getDataType() {
return provider.getRegisterDataType(register); 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) { public void setRepresentation(String representation) {
provider.writeRegisterValueRepresentation(register, representation); provider.writeRegisterValueRepresentation(register, representation);
} }
/**
* Check if the register's value can be set via its data type's representation
*
* @return
*/
public boolean isRepresentationEditable() { public boolean isRepresentationEditable() {
return provider.canWriteRegisterRepresentation(register); return provider.canWriteRegisterRepresentation(register);
} }
/**
* Get the value of the register as represented by its data type
*
* @return the value
*/
public String getRepresentation() { public String getRepresentation() {
return provider.getRegisterValueRepresentation(register); return provider.getRegisterValueRepresentation(register);
} }
/**
* Check if the register's value is (completely) known
*
* @return true if known
*/
public boolean isKnown() { public boolean isKnown() {
return provider.isRegisterKnown(register); return provider.isRegisterKnown(register);
} }
/**
* Check if the register's value changed since last navigation or command
*
* @return true if changed
*/
public boolean isChanged() { public boolean isChanged() {
return provider.isRegisterChanged(register); return provider.isRegisterChanged(register);
} }
/**
* Get the table's current coordinates (usually also the tool's)
*
* @return the coordinates
*/
public DebuggerCoordinates getCurrent() {
return provider.getCurrent();
}
} }

View file

@ -36,8 +36,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.dbg.DebugModelConventions; import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetStackFrame;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@ -116,8 +115,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
protected static class StackTableModel protected static class StackTableModel
extends DefaultEnumeratedColumnTableModel<StackTableColumns, StackFrameRow> { extends DefaultEnumeratedColumnTableModel<StackTableColumns, StackFrameRow> {
public StackTableModel() { public StackTableModel(PluginTool tool) {
super("Stack", StackTableColumns.class); super(tool, "Stack", StackTableColumns.class);
} }
@Override @Override
@ -243,7 +242,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>(); private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
protected final StackTableModel stackTableModel = new StackTableModel(); protected final StackTableModel stackTableModel;
protected GhidraTable stackTable; protected GhidraTable stackTable;
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel; protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
@ -253,7 +252,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
public DebuggerStackProvider(DebuggerStackPlugin plugin) { public DebuggerStackProvider(DebuggerStackPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName());
//this.plugin = plugin; stackTableModel = new StackTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);

View file

@ -92,8 +92,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> { ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
public ThreadTableModel(DebuggerThreadsProvider provider) { public ThreadTableModel(DebuggerThreadsProvider provider) {
super("Threads", ThreadTableColumns.class, TraceThread::getObjectKey, super(provider.getTool(), "Threads", ThreadTableColumns.class,
t -> new ThreadRow(provider.modelService, t)); TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
} }
} }

View file

@ -30,6 +30,7 @@ import com.google.common.collect.Collections2;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType; import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener; import ghidra.trace.model.TraceDomainObjectListener;
@ -130,8 +131,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
} }
} }
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel = protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
protected final GTable snapshotTable; protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel; protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
protected boolean hideScratch = true; protected boolean hideScratch = true;
@ -141,8 +141,10 @@ public class DebuggerSnapshotTablePanel extends JPanel {
protected final SnapshotListener listener = new SnapshotListener(); protected final SnapshotListener listener = new SnapshotListener();
public DebuggerSnapshotTablePanel() { public DebuggerSnapshotTablePanel(PluginTool tool) {
super(new BorderLayout()); super(new BorderLayout());
snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>(tool, "Snapshots", SnapshotTableColumns.class);
snapshotTable = new GTable(snapshotTableModel); snapshotTable = new GTable(snapshotTableModel);
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(snapshotTable)); add(new JScrollPane(snapshotTable));

View file

@ -60,7 +60,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Wiring autoServiceWiring; private final Wiring autoServiceWiring;
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel(); /*testing*/ final DebuggerSnapshotTablePanel mainPanel;
private DebuggerSnapActionContext myActionContext; private DebuggerSnapActionContext myActionContext;
@ -80,6 +80,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
setHelpLocation(HELP_PROVIDER_TIME); setHelpLocation(HELP_PROVIDER_TIME);
setWindowMenuGroup(DebuggerPluginPackage.NAME); setWindowMenuGroup(DebuggerPluginPackage.NAME);
mainPanel = new DebuggerSnapshotTablePanel(tool);
buildMainPanel(); buildMainPanel();
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap()); myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());

View file

@ -86,7 +86,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1))); opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1)));
{ {
snapshotPanel = new DebuggerSnapshotTablePanel(); snapshotPanel = new DebuggerSnapshotTablePanel(tool);
workPanel.add(snapshotPanel, BorderLayout.CENTER); workPanel.add(snapshotPanel, BorderLayout.CENTER);
} }

View file

@ -197,8 +197,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
protected static class WatchTableModel protected static class WatchTableModel
extends DefaultEnumeratedColumnTableModel<WatchTableColumns, WatchRow> { extends DefaultEnumeratedColumnTableModel<WatchTableColumns, WatchRow> {
public WatchTableModel() { public WatchTableModel(PluginTool tool) {
super("Watches", WatchTableColumns.class); super(tool, "Watches", WatchTableColumns.class);
} }
} }
@ -346,7 +346,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private JPanel mainPanel = new JPanel(new BorderLayout()); private JPanel mainPanel = new JPanel(new BorderLayout());
protected final WatchTableModel watchTableModel = new WatchTableModel(); protected final WatchTableModel watchTableModel;
protected GhidraTable watchTable; protected GhidraTable watchTable;
protected GhidraTableFilterPanel<WatchRow> watchFilterPanel; protected GhidraTableFilterPanel<WatchRow> watchFilterPanel;
@ -366,6 +366,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) { public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName()); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName());
this.plugin = plugin; this.plugin = plugin;
watchTableModel = new WatchTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);

View file

@ -31,7 +31,7 @@ import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.pcode.exec.*; 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.exec.trace.TraceSleighUtils;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@ -170,21 +170,20 @@ public class WatchRow {
return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length); return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length);
} }
public static class ReadDepsTraceBytesPcodeExecutorState public static class ReadDepsTraceBytesPcodeExecutorStatePiece
extends TraceBytesPcodeExecutorState { extends DirectBytesTracePcodeExecutorStatePiece {
private AddressSet reads = new AddressSet(); private AddressSet reads = new AddressSet();
public ReadDepsTraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) { int frame) {
super(trace, snap, thread, frame); super(trace, snap, thread, frame);
} }
@Override @Override
public byte[] getVar(AddressSpace space, long offset, int size, public byte[] getVar(AddressSpace space, long offset, int size, boolean quantize) {
boolean truncateAddressableUnit) { byte[] data = super.getVar(space, offset, size, quantize);
byte[] data = super.getVar(space, offset, size, truncateAddressableUnit);
if (space.isMemorySpace()) { if (space.isMemorySpace()) {
offset = truncateOffset(space, offset); offset = quantizeOffset(space, offset);
} }
if (space.isMemorySpace() || space.isRegisterSpace()) { if (space.isMemorySpace() || space.isRegisterSpace()) {
try { try {
@ -213,42 +212,42 @@ public class WatchRow {
public static class ReadDepsPcodeExecutor public static class ReadDepsPcodeExecutor
extends PcodeExecutor<Pair<byte[], Address>> { 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, SleighLanguage language, PairedPcodeArithmetic<byte[], Address> arithmetic,
PcodeExecutorState<Pair<byte[], Address>> state) { PcodeExecutorState<Pair<byte[], Address>> state) {
super(language, arithmetic, state); super(language, arithmetic, state);
this.depsState = depsState; this.depsPiece = depsState;
} }
@Override @Override
public PcodeFrame execute(PcodeProgram program, public PcodeFrame execute(PcodeProgram program,
PcodeUseropLibrary<Pair<byte[], Address>> library) { PcodeUseropLibrary<Pair<byte[], Address>> library) {
depsState.reset(); depsPiece.reset();
return super.execute(program, library); return super.execute(program, library);
} }
public AddressSet getReads() { public AddressSet getReads() {
return depsState.getReads(); return depsPiece.getReads();
} }
} }
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor( protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(
DebuggerCoordinates coordinates) { DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); Trace trace = coordinates.getTrace();
ReadDepsTraceBytesPcodeExecutorState state = ReadDepsTraceBytesPcodeExecutorStatePiece piece =
new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(), new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame()); coordinates.getThread(), coordinates.getFrame());
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) { if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Watch expressions require a SLEIGH language"); throw new IllegalArgumentException("Watch expressions require a SLEIGH language");
} }
PcodeExecutorState<Pair<byte[], Address>> paired = PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
state.paired(new AddressOfPcodeExecutorState(language.isBigEndian())); .paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian()));
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>( PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE); 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) { public void setCoordinates(DebuggerCoordinates coordinates) {
@ -271,7 +270,7 @@ public class WatchRow {
recompile(); recompile();
} }
if (coordinates.isAliveAndReadsPresent()) { if (coordinates.isAliveAndReadsPresent()) {
asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates); asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates);
} }
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());

View file

@ -20,7 +20,7 @@ import java.util.concurrent.*;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.AccessPcodeExecutionException; 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.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@ -31,8 +31,17 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction; 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 { abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space, public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
@ -86,30 +95,47 @@ public abstract class AbstractReadsTargetPcodeExecutorState
protected final TraceRecorder recorder; protected final TraceRecorder recorder;
protected final PluginTool tool; 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) { TraceThread thread, int frame, TraceRecorder recorder) {
super(trace, snap, thread, frame); super(trace, snap, thread, frame);
this.tool = tool; this.tool = tool;
this.recorder = recorder; 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) { * Get the recorder associated with the trace
return spaces.computeIfAbsent(space, s -> { *
TraceMemorySpace tms; * @return this is used to check for and perform live reads
if (s.isUniqueSpace()) { */
tms = null; 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);
});
} }
} }

View file

@ -17,67 +17,70 @@ package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.BytesPcodeThread; import ghidra.pcode.emu.*;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.TracePcodeEmulator;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace; 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.TraceThread;
/** /**
* A trace emulator that knows how to read target memory when necessary * A trace emulator that knows how to read target memory when necessary
* *
* <p> * <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 * 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 * UI. It blocks on target reads so that execution can proceed synchronously. Probably the most
* suitable option is to use a background task. * 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 PluginTool tool;
protected final TraceRecorder recorder; 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) { TraceRecorder recorder) {
super(trace, snap); super(trace, snap);
this.tool = tool; this.tool = tool;
this.recorder = recorder; this.recorder = recorder;
} }
protected boolean isRegisterKnown(String threadName, Register register) { @Override
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName); public PluginTool getTool() {
TraceMemoryRegisterSpace space = return tool;
trace.getMemoryManager().getMemoryRegisterSpace(thread, false); }
if (space == null) {
return false; @Override
} public TraceRecorder getRecorder() {
return space.getState(snap, register) == TraceMemoryState.KNOWN; return recorder;
} }
@Override @Override
protected BytesPcodeThread createThread(String name) { protected BytesPcodeThread createThread(String name) {
BytesPcodeThread thread = super.createThread(name); BytesPcodeThread thread = super.createThread(name);
Register contextreg = language.getContextBaseRegister(); initializeThreadContext(thread);
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);
}
}
return thread; return thread;
} }
@Override @Override
protected PcodeExecutorState<byte[]> createSharedState() { public TracePcodeExecutorState<byte[]> createSharedState() {
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder); return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder);
} }
@Override @Override
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) { public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread = TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()); trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0, return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,

View file

@ -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);
}
}

View file

@ -17,14 +17,19 @@ package ghidra.app.plugin.core.debug.service.emulation;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent; 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.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*; 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.CompareResult;
import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
@ -74,20 +79,21 @@ import ghidra.util.task.TaskMonitor;
}) })
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService { public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
protected static final int MAX_CACHE_SIZE = 5; protected static final int MAX_CACHE_SIZE = 5;
protected static long nextSnap = Long.MIN_VALUE; // HACK
protected static class CacheKey implements Comparable<CacheKey> { protected static class CacheKey implements Comparable<CacheKey> {
protected final Trace trace; protected final Trace trace;
protected final TraceSchedule time; protected final TraceSchedule time;
private final int hashCode;
public CacheKey(Trace trace, TraceSchedule time) { public CacheKey(Trace trace, TraceSchedule time) {
this.trace = trace; this.trace = Objects.requireNonNull(trace);
this.time = time; this.time = Objects.requireNonNull(time);
this.hashCode = Objects.hash(trace, time);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(trace, time); return hashCode;
} }
@Override @Override
@ -130,9 +136,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
protected static class CachedEmulator { protected static class CachedEmulator {
final DebuggerTracePcodeEmulator emulator; final DebuggerPcodeMachine<?> emulator;
public CachedEmulator(DebuggerTracePcodeEmulator emulator) { public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
this.emulator = 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 Set<CacheKey> eldest = new LinkedHashSet<>();
protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>(); protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>();
protected final AsyncLazyMap<CacheKey, Long> requests = protected final AsyncLazyMap<CacheKey, Long> requests =
@ -180,6 +189,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
DockingAction actionEmulateProgram; DockingAction actionEmulateProgram;
DockingAction actionEmulateAddThread; DockingAction actionEmulateAddThread;
Map<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> //
actionsChooseEmulatorFactory = new HashMap<>();
final ChangeListener classChangeListener = this::classesChanged;
public DebuggerEmulationServicePlugin(PluginTool tool) { public DebuggerEmulationServicePlugin(PluginTool tool) {
super(tool); super(tool);
@ -205,6 +218,46 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
.popupWhen(this::emulateAddThreadEnabled) .popupWhen(this::emulateAddThreadEnabled)
.onAction(this::emulateAddThreadActivated) .onAction(this::emulateAddThreadActivated)
.buildAndInstall(tool); .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) { private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) {
@ -228,7 +281,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = null; Trace trace = null;
try { try {
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this); trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
traceManager.openTrace(trace); traceManager.openTrace(trace);
traceManager.activateTrace(trace); traceManager.activateTrace(trace);
} }
@ -275,7 +327,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
private void emulateAddThreadActivated(ProgramLocationActionContext ctx) { private void emulateAddThreadActivated(ProgramLocationActionContext ctx) {
Program programOrView = ctx.getProgram(); Program programOrView = ctx.getProgram();
if (programOrView instanceof TraceProgramView) { if (programOrView instanceof TraceProgramView) {
TraceProgramView view = (TraceProgramView) programOrView; 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) { protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
synchronized (cache) { synchronized (cache) {
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key); Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
@ -378,7 +458,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = key.trace; Trace trace = key.trace;
TraceSchedule time = key.time; TraceSchedule time = key.time;
CachedEmulator ce; CachedEmulator ce;
DebuggerTracePcodeEmulator emu; DebuggerPcodeMachine<?> emu;
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key); Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
if (ancestor != null) { if (ancestor != null) {
CacheKey prevKey = ancestor.getKey(); CacheKey prevKey = ancestor.getKey();
@ -396,7 +476,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
time.finish(trace, prevKey.time, emu, monitor); time.finish(trace, prevKey.time, emu, monitor);
} }
else { else {
emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(), emu = emulatorFactory.create(tool, trace, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace)); modelService == null ? null : modelService.getRecorder(trace));
ce = new CachedEmulator(emu); ce = new CachedEmulator(emu);
monitor.initialize(time.totalTickCount()); monitor.initialize(time.totalTickCount());
@ -405,7 +485,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
TraceSnapshot destSnap; TraceSnapshot destSnap;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) { try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
destSnap = findScratch(trace, time); destSnap = findScratch(trace, time);
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false); emu.writeDown(trace, destSnap.getKey(), time.getSnap());
} }
synchronized (cache) { synchronized (cache) {
@ -436,7 +516,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
} }
@Override @Override
public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) { public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
CachedEmulator ce = cache.get(new CacheKey(trace, time)); CachedEmulator ce = cache.get(new CacheKey(trace, time));
return ce == null ? null : ce.emulator; return ce == null ? null : ce.emulator;
} }

View file

@ -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);
}

View file

@ -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();
}

View file

@ -15,123 +15,29 @@
*/ */
package ghidra.app.plugin.core.debug.service.emulation; 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.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
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.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread; 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, public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) { TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder); super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
} recorder));
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetMemoryCachedSpace(language, s, tms, snap);
} }
} }

View file

@ -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);
}
};
}
}

View file

@ -15,63 +15,29 @@
*/ */
package ghidra.app.plugin.core.debug.service.emulation; package ghidra.app.plugin.core.debug.service.emulation;
import java.util.HashSet;
import java.util.Set;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread; 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, public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) { TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder); super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
} recorder));
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetRegistersCachedSpace(language, s, tms, snap);
} }
} }

View file

@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.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);
}
};
}
}

View file

@ -208,8 +208,7 @@ public class DebuggerModelServicePlugin extends Plugin
protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances(); protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances();
protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders(); protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
protected final DebuggerSelectMappingOfferDialog offerDialog = protected final DebuggerSelectMappingOfferDialog offerDialog;
new DebuggerSelectMappingOfferDialog();
protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog(); protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
DockingAction actionDisconnectAll; DockingAction actionDisconnectAll;
@ -218,7 +217,7 @@ public class DebuggerModelServicePlugin extends Plugin
public DebuggerModelServicePlugin(PluginTool tool) { public DebuggerModelServicePlugin(PluginTool tool) {
super(tool); super(tool);
offerDialog = new DebuggerSelectMappingOfferDialog(tool);
ClassSearcher.addChangeListener(classChangeListener); ClassSearcher.addChangeListener(classChangeListener);
refreshFactoryInstances(); refreshFactoryInstances();
connectDialog.setModelService(this); connectDialog.setModelService(this);

View file

@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer; import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
@ -126,8 +127,8 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
public static class OfferTableModel public static class OfferTableModel
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerMappingOffer> { extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerMappingOffer> {
public OfferTableModel() { public OfferTableModel(PluginTool tool) {
super("Offers", OfferTableColumns.class); super(tool, "Offers", OfferTableColumns.class);
} }
@Override @Override
@ -138,14 +139,13 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
} }
public static class OfferPanel extends JPanel { public static class OfferPanel extends JPanel {
private final OfferTableModel offerTableModel = new OfferTableModel(); private final OfferTableModel offerTableModel;
private final GhidraTable offerTable = new GhidraTable(offerTableModel); private final GhidraTable offerTable;
private final GhidraTableFilterPanel<DebuggerMappingOffer> offerTableFilterPanel = private final GhidraTableFilterPanel<DebuggerMappingOffer> offerTableFilterPanel;
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
private final JLabel descLabel = new JLabel(); private final JLabel descLabel = new JLabel();
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers"); private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
private final JScrollPane scrollPane = new JScrollPane(offerTable) { private final JScrollPane scrollPane = new JScrollPane() {
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize(); Dimension pref = super.getPreferredSize();
@ -170,7 +170,12 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider {
private LanguageID preferredLangID; private LanguageID preferredLangID;
private CompilerSpecID preferredCsID; 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()); JPanel descPanel = new JPanel(new BorderLayout());
descPanel.setBorder(BorderFactory.createTitledBorder("Description")); descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
descPanel.add(descLabel, BorderLayout.CENTER); 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; private boolean isCancelled = false;
protected DebuggerSelectMappingOfferDialog() { protected DebuggerSelectMappingOfferDialog(PluginTool tool) {
super(DebuggerResources.AbstractRecordAction.NAME, true, false, true, false); super(DebuggerResources.AbstractRecordAction.NAME, true, false, true, false);
offerPanel = new OfferPanel(tool);
populateComponents(); populateComponents();
} }

View file

@ -21,6 +21,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableCo
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel; import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer; import ghidra.async.AsyncTimer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Swing; import ghidra.util.Swing;
public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, K, R, T> 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); 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) { Function<T, K> keyFunc, Function<T, R> wrapper) {
super(name, colType, keyFunc, wrapper); super(tool, name, colType, keyFunc, wrapper);
debouncer.addListener(this::settled); debouncer.addListener(this::settled);
} }

View file

@ -15,19 +15,59 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; 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) @ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
public interface DebuggerEmulationService { 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 * 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 * @param time the time coordinates, including initial snap, steps, and p-code steps
* @return the copied p-code frame * @return the copied p-code frame
*/ */
DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time); DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time);
} }

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode; 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 * until the computation has been performed -- assuming the requested variable actually depends on
* that computation. * 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 * @param <T> the type of values in the state
*/ */
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> { public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
public AsyncPcodeExecutor(SleighLanguage language, public AsyncPcodeExecutor(SleighLanguage language,
PcodeArithmetic<CompletableFuture<T>> arithmetic, PcodeArithmetic<CompletableFuture<T>> arithmetic,
PcodeExecutorStatePiece<CompletableFuture<T>, CompletableFuture<T>> state) { PcodeExecutorState<CompletableFuture<T>> state) {
super(language, arithmetic, state); super(language, arithmetic, state);
} }
@ -73,7 +81,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
Varnode condVar = op.getInput(1); Varnode condVar = op.getInput(1);
CompletableFuture<T> cond = state.getVar(condVar); CompletableFuture<T> cond = state.getVar(condVar);
return cond.thenAccept(c -> { return cond.thenAccept(c -> {
if (arithmetic.isTrue(cond)) { if (arithmetic.isTrue(cond, Purpose.CONDITION)) {
executeBranch(op, frame); executeBranch(op, frame);
} }
}); });

View file

@ -15,21 +15,23 @@
*/ */
package ghidra.pcode.exec; package ghidra.pcode.exec;
import java.math.BigInteger; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.pcode.opbehavior.BinaryOpBehavior; import ghidra.program.model.lang.Endian;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.lang.Language; 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 class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<CompletableFuture<T>> {
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_BE = public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_BE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN); new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN);
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_LE = public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_LE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN); 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) { public static AsyncWrappedPcodeArithmetic<byte[]> forEndian(boolean isBigEndian) {
return isBigEndian ? BYTES_BE : BYTES_LE; return isBigEndian ? BYTES_BE : BYTES_LE;
@ -46,46 +48,71 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
} }
@Override @Override
public CompletableFuture<T> unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, public boolean equals(Object obj) {
CompletableFuture<T> in1) { if (this == obj) {
return in1.thenApply(t1 -> arithmetic.unaryOp(op, sizeout, sizein1, t1)); return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
AsyncWrappedPcodeArithmetic<?> that = (AsyncWrappedPcodeArithmetic<?>) obj;
return Objects.equals(this.arithmetic, that.arithmetic);
} }
@Override @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) { CompletableFuture<T> in1, int sizein2, CompletableFuture<T> in2) {
return in1.thenCombine(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 @Override
public CompletableFuture<T> fromConst(long value, int size) { public CompletableFuture<T> modBeforeStore(int sizeout, int sizeinAddress,
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size)); CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
} }
@Override @Override
public CompletableFuture<T> fromConst(BigInteger value, int size, boolean isContextreg) { public CompletableFuture<T> modAfterLoad(int sizeout, int sizeinAddress,
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size, isContextreg)); CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
} }
@Override @Override
public boolean isTrue(CompletableFuture<T> cond) { public CompletableFuture<T> fromConst(byte[] value) {
if (!cond.isDone()) { return CompletableFuture.completedFuture(arithmetic.fromConst(value));
throw new AssertionError("You need a better 8-ball"); }
@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 @Override
public BigInteger toConcrete(CompletableFuture<T> cond, boolean isContextreg) { public long sizeOf(CompletableFuture<T> value) {
if (!cond.isDone()) { if (!value.isDone()) {
throw new AssertionError("You need a better 8-ball"); // 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 @Override
public CompletableFuture<T> sizeOf(CompletableFuture<T> value) { public CompletableFuture<T> sizeOfAbstract(CompletableFuture<T> value) {
return value.thenApply(v -> arithmetic.sizeOf(v)); return value.thenApply(v -> arithmetic.sizeOfAbstract(v));
} }
} }

View file

@ -17,9 +17,14 @@ package ghidra.pcode.exec;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class AsyncWrappedPcodeExecutorState<T> extends AsyncWrappedPcodeExecutorStatePiece<T, T> /**
implements PcodeExecutorState<CompletableFuture<T>> { * The state for a {@link AsyncWrappedPcodeExecutorStatePiece}
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> state) { *
super(state); * @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));
} }
} }

View file

@ -19,17 +19,38 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier; import java.util.function.Supplier;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer; 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> public class AsyncWrappedPcodeExecutorStatePiece<A, T>
implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> { implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> {
protected final PcodeExecutorStatePiece<A, T> state; protected final PcodeExecutorStatePiece<A, T> state;
protected final AsyncWrappedPcodeArithmetic<A> addressArithmetic;
protected final AsyncWrappedPcodeArithmetic<T> arithmetic;
private CompletableFuture<?> lastWrite = AsyncUtils.NIL; private CompletableFuture<?> lastWrite = AsyncUtils.NIL;
public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, T> state) { public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, T> state) {
this.state = 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() { protected boolean isWriteDone() {
@ -45,41 +66,36 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
} }
protected CompletableFuture<?> doSetVar(AddressSpace space, CompletableFuture<A> offset, 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 -> { return offset.thenCompose(off -> val.thenAccept(v -> {
state.setVar(space, off, size, truncateAddressableUnit, v); state.setVar(space, off, size, quantize, v);
})); }));
} }
@Override @Override
public void setVar(AddressSpace space, CompletableFuture<A> offset, int size, public void setVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean truncateAddressableUnit, CompletableFuture<T> val) { boolean quantize, CompletableFuture<T> val) {
nextWrite(() -> doSetVar(space, offset, size, truncateAddressableUnit, val)); nextWrite(() -> doSetVar(space, offset, size, quantize, val));
} }
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset, protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
int size, boolean truncateAddressableUnit) { int size, boolean quantize) {
return offset.thenApply(off -> { return offset.thenApply(off -> {
return state.getVar(space, off, size, truncateAddressableUnit); return state.getVar(space, off, size, quantize);
}); });
} }
@Override @Override
public CompletableFuture<T> getVar(AddressSpace space, CompletableFuture<A> offset, int size, public CompletableFuture<T> getVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean truncateAddressableUnit) { boolean quantize) {
return nextRead(() -> doGetVar(space, offset, size, truncateAddressableUnit)); return nextRead(() -> doGetVar(space, offset, size, quantize));
} }
@Override @Override
public CompletableFuture<A> longToOffset(AddressSpace space, long l) { public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return CompletableFuture.completedFuture(state.longToOffset(space, l));
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
if (!isWriteDone()) { if (!isWriteDone()) {
throw new AssertionError("An async write is still pending"); throw new AssertionError("An async write is still pending");
} }
return state.getConcreteBuffer(address); return state.getConcreteBuffer(address, purpose);
} }
} }

View file

@ -19,12 +19,22 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; 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.program.model.lang.Language;
import ghidra.trace.model.Trace; 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( public static AsyncPcodeExecutor<byte[]> executorForCoordinates(
DebuggerCoordinates coordinates) { DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); Trace trace = coordinates.getTrace();
@ -39,7 +49,7 @@ public enum TracePcodeUtils {
PcodeExecutorState<CompletableFuture<byte[]>> state; PcodeExecutorState<CompletableFuture<byte[]>> state;
if (coordinates.getRecorder() == null) { if (coordinates.getRecorder() == null) {
state = new AsyncWrappedPcodeExecutorState<>( state = new AsyncWrappedPcodeExecutorState<>(
new TraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(), new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame())); coordinates.getThread(), coordinates.getFrame()));
} }
else { else {

View file

@ -15,128 +15,26 @@
*/ */
package ghidra.pcode.exec; package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder; 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.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
/**
* A state composing a single {@link TraceRecorderAsyncPcodeExecutorStatePiece}
*/
public class TraceRecorderAsyncPcodeExecutorState public class TraceRecorderAsyncPcodeExecutorState
extends AsyncWrappedPcodeExecutorState<byte[]> { extends DefaultPcodeExecutorState<CompletableFuture<byte[]>> {
private final TraceRecorder recorder; /**
private final TraceBytesPcodeExecutorState traceState; * Create the state
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState; *
* @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, public TraceRecorderAsyncPcodeExecutorState(TraceRecorder recorder, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
super(new TraceBytesPcodeExecutorState(recorder.getTrace(), snap, thread, frame)); super(new TraceRecorderAsyncPcodeExecutorStatePiece(recorder, 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);
});
} }
} }

View file

@ -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);
});
}
}

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -567,7 +567,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
@After @After
public void tearDown() { public void tearDown() {
waitForTasks(); waitForTasks();
runSwing(() -> traceManager.setSaveTracesByDefault(false)); runSwing(() -> {
if (traceManager == null) {
return;
}
traceManager.setSaveTracesByDefault(false);
});
if (tb != null) { if (tb != null) {
if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) { if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) {

View file

@ -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.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; 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.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.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService;
@ -142,10 +142,10 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
traceManager.activateTime(schedule2); traceManager.activateTime(schedule2);
waitForPass(() -> assertEquals(schedule2, pcodeProvider.current.getTime())); waitForPass(() -> assertEquals(schedule2, pcodeProvider.current.getTime()));
DebuggerTracePcodeEmulator emu = DebuggerPcodeMachine<?> emu =
waitForValue(() -> emuService.getCachedEmulator(tb.trace, schedule2)); waitForValue(() -> emuService.getCachedEmulator(tb.trace, schedule2));
assertNotNull(emu); assertNotNull(emu);
PcodeThread<byte[]> et = emu.getThread(thread.getPath(), false); PcodeThread<?> et = emu.getThread(thread.getPath(), false);
waitForPass(() -> assertNull(et.getFrame())); waitForPass(() -> assertNull(et.getFrame()));
/** /**
@ -171,7 +171,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh, PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh,
PcodeUseropLibrary.nil()); PcodeUseropLibrary.nil());
PcodeExecutor<byte[]> executor = PcodeExecutor<byte[]> executor =
new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null); new PcodeExecutor<>(language, BytesPcodeArithmetic.BIG_ENDIAN, null);
PcodeFrame frame = executor.begin(prog); PcodeFrame frame = executor.begin(prog);
PcodeRowHtmlFormatter formatter = pcodeProvider.new PcodeRowHtmlFormatter(language, frame); PcodeRowHtmlFormatter formatter = pcodeProvider.new PcodeRowHtmlFormatter(language, frame);
return formatter.getRows(); return formatter.getRows();

View file

@ -33,7 +33,7 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.pcode.exec.AsyncPcodeExecutor; import ghidra.pcode.exec.AsyncPcodeExecutor;
import ghidra.pcode.exec.TracePcodeUtils; import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
@ -143,8 +143,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread // NB. TraceManager should automatically activate the first thread
TraceThread thread = tb.getOrAddThread("Threads[0]", 0); TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor = AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123"); asm.assemble(tb.addr(0x00400000), "imm r0,#123");
@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread // NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0); thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor = AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123"); asm.assemble(tb.addr(0x00400000), "imm r0,#123");

View file

@ -29,6 +29,7 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.ActionSource; import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetRegisterBankInThread; import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;

View file

@ -24,8 +24,17 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread; 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 { protected class CheckedCachedSpace extends CachedSpace {
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source, 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) { TraceThread thread, int frame) {
super(trace, snap, thread, frame); super(trace, snap, thread, frame);
} }
@Override @Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new CheckedCachedSpace(language, space, backing, snap); 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, protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized); AddressSet uninitialized);
} }

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -20,9 +20,9 @@ import java.nio.ByteBuffer;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong; 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.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.AddressSet;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@ -32,22 +32,23 @@ import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities; 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> * <p>
* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but * 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 * rather are cached in this state. If desired, those cached writes can be written back out at a
* later time. * later time.
*/ */
public class TraceCachedWriteBytesPcodeExecutorState public class BytesTracePcodeExecutorStatePiece
extends AbstractBytesPcodeExecutorState<TraceMemorySpace, CachedSpace> { extends AbstractBytesPcodeExecutorStatePiece<CachedSpace>
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
protected final Trace trace; protected final Trace trace;
protected final long snap; protected final long snap;
protected final TraceThread thread; protected final TraceThread thread;
protected final int frame; protected final int frame;
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) { int frame) {
super(trace.getBaseLanguage()); super(trace.getBaseLanguage());
this.trace = trace; this.trace = trace;
@ -56,7 +57,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
this.frame = frame; 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 RangeSet<UnsignedLong> written = TreeRangeSet.create();
protected final long snap; protected final long snap;
@ -126,50 +127,69 @@ public class TraceCachedWriteBytesPcodeExecutorState
} }
} }
/**
* Get the state's source trace
*
* @return the trace
*/
public Trace getTrace() { public Trace getTrace() {
return trace; return trace;
} }
/**
* Get the source snap
*
* @return the snap
*/
public long getSnap() { public long getSnap() {
return snap; return snap;
} }
/**
* Get the source thread, if a local state
*
* @return the thread
*/
public TraceThread getThread() { public TraceThread getThread() {
return thread; return thread;
} }
/**
* Get the source frame, if a local state
*
* @return the frame, probably 0
*/
public int getFrame() { public int getFrame() {
return frame; return frame;
} }
/** @Override
* Write the accumulated writes into the given trace public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
*
* <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) {
if (trace.getBaseLanguage() != language) { if (trace.getBaseLanguage() != language) {
throw new IllegalArgumentException("Destination trace must be same language as source"); 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); cached.writeDown(trace, snap, thread, frame);
} }
} }
@Override /**
protected TraceMemorySpace getBacking(AddressSpace space) { * A space map which binds spaces to corresponding spaces in the trace
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false); */
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 @Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new CachedSpace(language, space, backing, snap); return new TraceBackedSpaceMap();
} }
} }

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -17,14 +17,14 @@ package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*; 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.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace; 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.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport; 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(); protected final SemisparseByteArray unique = new SemisparseByteArray();
private final Trace trace; private final Trace trace;
@ -43,8 +55,10 @@ public class TraceBytesPcodeExecutorState
private final DefaultTraceTimeViewport viewport; private final DefaultTraceTimeViewport viewport;
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { protected DirectBytesTracePcodeExecutorStatePiece(Language language,
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage())); PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread,
int frame) {
super(language, arithmetic, arithmetic);
this.trace = trace; this.trace = trace;
this.snap = snap; this.snap = snap;
this.thread = thread; this.thread = thread;
@ -54,48 +68,65 @@ public class TraceBytesPcodeExecutorState
this.viewport.setSnap(snap); this.viewport.setSnap(snap);
} }
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() { protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap,
return new PairedPcodeExecutorState<>(this, TraceThread thread, int frame) {
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)) { this(language, BytesPcodeArithmetic.forLanguage(language), 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);
}
};
} }
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() { public Trace getTrace() {
return trace; return trace;
} }
/**
* Re-bind this state to another snap
*
* @param snap the new snap
*/
public void setSnap(long snap) { public void setSnap(long snap) {
this.snap = snap; this.snap = snap;
this.viewport.setSnap(snap); this.viewport.setSnap(snap);
} }
/**
* Get the current snap
*
* @return the snap
*/
public long getSnap() { public long getSnap() {
return snap; return snap;
} }
/**
* Re-bind this state to another thread
*
* @param thread the new thread
*/
public void setThread(TraceThread thread) { public void setThread(TraceThread thread) {
if (thread != null & thread.getTrace() != trace) { if (thread != null & thread.getTrace() != trace) {
throw new IllegalArgumentException("Thread, if given, must be part of the same trace"); throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
@ -103,28 +134,33 @@ public class TraceBytesPcodeExecutorState
this.thread = thread; this.thread = thread;
} }
/**
* Get the current thread
*
* @return the thread
*/
public TraceThread getThread() { public TraceThread getThread() {
return thread; return thread;
} }
/**
* Re-bind this state to another frame
*
* @param frame the new frame
*/
public void setFrame(int frame) { public void setFrame(int frame) {
this.frame = frame; this.frame = frame;
} }
/**
* Get the current frame
*
* @return the frame
*/
public int getFrame() { public int getFrame() {
return frame; 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 @Override
protected void setUnique(long offset, int size, byte[] val) { protected void setUnique(long offset, int size, byte[] val) {
assert size == val.length; assert size == val.length;
@ -164,7 +200,7 @@ public class TraceBytesPcodeExecutorState
} }
@Override @Override
public MemBuffer getConcreteBuffer(Address address) { public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return trace.getMemoryManager().getBufferAt(snap, address); return trace.getMemoryManager().getBufferAt(snap, address);
} }
} }

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -15,31 +15,26 @@
*/ */
package ghidra.pcode.exec.trace; 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.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece}
*/
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState 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, public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
super(trace, snap, thread, frame); super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(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.");
} }
} }

View file

@ -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.");
}
}

View file

@ -15,46 +15,26 @@
*/ */
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace; 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.trace.model.thread.TraceThread;
/**
* A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState}
*/
public class 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, public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
super(trace, snap, thread, frame); super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(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);
} }
} }

View file

@ -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);
}
}

View file

@ -17,22 +17,41 @@ package ghidra.pcode.exec.trace;
import java.math.BigInteger; import java.math.BigInteger;
import ghidra.pcode.exec.ConcretionError;
import ghidra.pcode.exec.PcodeArithmetic; import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.opbehavior.BinaryOpBehavior; import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior; import ghidra.program.model.lang.Endian;
import ghidra.trace.model.memory.TraceMemoryState; 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> { public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemoryState> {
/** The singleton instance */
INSTANCE; INSTANCE;
@Override @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) { TraceMemoryState in1) {
return in1; return in1;
} }
@Override @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) { TraceMemoryState in1, int sizein2, TraceMemoryState in2) {
if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) { if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) {
return TraceMemoryState.KNOWN; return TraceMemoryState.KNOWN;
@ -41,7 +60,22 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
} }
@Override @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; return TraceMemoryState.KNOWN;
} }
@ -51,17 +85,17 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
} }
@Override @Override
public boolean isTrue(TraceMemoryState cond) { public TraceMemoryState fromConst(long value, int size) {
throw new AssertionError("Cannot decide branches using TraceMemoryState"); return TraceMemoryState.KNOWN;
} }
@Override @Override
public BigInteger toConcrete(TraceMemoryState value, boolean isContextreg) { public byte[] toConcrete(TraceMemoryState value, Purpose purpose) {
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'"); throw new ConcretionError("Cannot make TraceMemoryState concrete", purpose);
} }
@Override @Override
public TraceMemoryState sizeOf(TraceMemoryState value) { public long sizeOf(TraceMemoryState value) {
throw new AssertionError("Cannot get size of a TraceMemoryState"); throw new AssertionError("Cannot get size of a TraceMemoryState");
} }
} }

View file

@ -20,8 +20,8 @@ import java.util.Map;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong; import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece; import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
@ -30,6 +30,18 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport; 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 public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> { AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
@ -43,7 +55,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) { int frame) {
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE); super(trace.getBaseLanguage(),
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE);
this.trace = trace; this.trace = trace;
this.snap = snap; this.snap = snap;
this.thread = thread; 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 @Override
protected void setUnique(long offset, int size, TraceMemoryState val) { protected void setUnique(long offset, int size, TraceMemoryState val) {
unique.put(range(offset, size), val); unique.put(range(offset, size), val);
@ -158,7 +162,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
} }
@Override @Override
public MemBuffer getConcreteBuffer(Address address) { public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer"); throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
} }
} }

View file

@ -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());
}
}
}
}

View file

@ -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.
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -32,9 +32,26 @@ import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
/**
* Various utilities for using Sleigh with traces
*/
public enum TraceSleighUtils { 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, public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace,
TraceThread thread, int frame, boolean toWrite) { TraceThread thread, int frame, boolean toWrite) {
if (space.isRegisterSpace()) { if (space.isRegisterSpace()) {
@ -47,10 +64,24 @@ public enum TraceSleighUtils {
return trace.getMemoryManager().getMemorySpace(space, toWrite); 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, public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
TraceBytesPcodeExecutorState state = DirectBytesTracePcodeExecutorState state =
new TraceBytesPcodeExecutorState(trace, snap, thread, frame); new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) { if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Trace must use a SLEIGH language"); throw new IllegalArgumentException("Trace must use a SLEIGH language");
@ -59,10 +90,24 @@ public enum TraceSleighUtils {
BytesPcodeArithmetic.forLanguage(language), state); 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( public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
Trace trace, long snap, TraceThread thread, int frame) { Trace trace, long snap, TraceThread thread, int frame) {
TraceBytesPcodeExecutorState state = DirectBytesTracePcodeExecutorState state =
new TraceBytesPcodeExecutorState(trace, snap, thread, frame); new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState(); PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) { if (!(language instanceof SleighLanguage)) {
@ -73,6 +118,16 @@ public enum TraceSleighUtils {
paired); 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, public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage(); SleighLanguage language = expr.getLanguage();
@ -84,6 +139,16 @@ public enum TraceSleighUtils {
return expr.evaluate(executor); 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, public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame); byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame);
@ -91,6 +156,16 @@ public enum TraceSleighUtils {
false); 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, public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) { Trace trace, long snap, TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage(); SleighLanguage language = expr.getLanguage();
@ -104,6 +179,16 @@ public enum TraceSleighUtils {
return expr.evaluate(executor); 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, public static Pair<BigInteger, TraceMemoryState> evaluateWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) { Trace trace, long snap, TraceThread thread, int frame) {
Pair<byte[], TraceMemoryState> bytesPair = Pair<byte[], TraceMemoryState> bytesPair =
@ -114,6 +199,16 @@ public enum TraceSleighUtils {
bytesPair.getRight()); 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, public static byte[] evaluateBytes(String expr, Trace trace, long snap, TraceThread thread,
int frame) { int frame) {
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
@ -125,6 +220,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame); 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, public static BigInteger evaluate(String expr, Trace trace, long snap, TraceThread thread,
int frame) { int frame) {
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
@ -135,6 +240,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame); 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, public static Entry<byte[], TraceMemoryState> evaluateBytesWithState(String expr, Trace trace,
long snap, TraceThread thread, int frame) { long snap, TraceThread thread, int frame) {
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
@ -146,6 +261,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame); 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, public static Entry<BigInteger, TraceMemoryState> evaluateWithState(String expr, Trace trace,
long snap, TraceThread thread, int frame) { long snap, TraceThread thread, int frame) {
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
@ -157,6 +282,18 @@ public enum TraceSleighUtils {
trace, snap, thread, frame); 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) { public static String generateExpressionForRange(Language language, AddressRange range) {
AddressSpace space = range.getAddressSpace(); AddressSpace space = range.getAddressSpace();
long length = range.getLength(); long length = range.getLength();

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -37,9 +37,19 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract
import ghidra.util.database.DBAnnotatedObject; import ghidra.util.database.DBAnnotatedObject;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; 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 { public enum DBTraceUtils {
; ;
/**
* A tuple used to index/locate a block in the trace's byte stores (memory manager)
*/
public static class OffsetSnap { public static class OffsetSnap {
public final long offset; public final long offset;
public final long snap; public final long snap;
@ -83,6 +93,9 @@ public enum DBTraceUtils {
} }
// TODO: Should this be in by default? // TODO: Should this be in by default?
/**
* A codec or URLs
*/
public static class URLDBFieldCodec<OT extends DBAnnotatedObject> public static class URLDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<URL, OT, StringField> { extends AbstractDBFieldCodec<URL, OT, StringField> {
public URLDBFieldCodec(Class<OT> objectType, Field field, int column) { 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> public static class LanguageIDDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<LanguageID, OT, StringField> { 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> public static class CompilerSpecIDDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<CompilerSpecID, OT, StringField> { 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> public abstract static class AbstractOffsetSnapDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<OffsetSnap, OT, BinaryField> { extends AbstractDBFieldCodec<OffsetSnap, OT, BinaryField> {
@ -248,6 +270,7 @@ public enum DBTraceUtils {
/** /**
* Codec for storing {@link OffsetSnap}s as {@link BinaryField}s. * Codec for storing {@link OffsetSnap}s as {@link BinaryField}s.
* *
* <p>
* Encodes the address space ID followed by the address then the snap. * Encodes the address space ID followed by the address then the snap.
* *
* @param <OT> the type of the object whose field is encoded/decoded. * @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> public static class RefTypeDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<RefType, OT, ByteField> { extends AbstractDBFieldCodec<RefType, OT, ByteField> {
public RefTypeDBFieldCodec(Class<OT> objectType, Field field, int column) { 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> { 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); 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); protected abstract V getValue(E entry);
/**
* Remove an entry from the map
*
* @param entry the entry
*/
protected abstract void remove(E 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); 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); 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); 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); 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); 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); 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); 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) { protected D getPreviousOrSame(D d) {
D prev = getPrevious(d); D prev = getPrevious(d);
if (prev == null) { if (prev == null) {
@ -338,6 +440,12 @@ public enum DBTraceUtils {
return prev; 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) { protected D getNextOrSame(D d) {
D next = getNext(d); D next = getNext(d);
if (next == null) { if (next == null) {
@ -346,15 +454,40 @@ public enum DBTraceUtils {
return next; 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) { protected boolean connects(R r1, R r2) {
return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 || return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 ||
getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 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) { public E set(R range, V value) {
return set(getLower(range), getUpper(range), 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) { public E set(D lower, D upper, V value) {
// Go one out to find abutting ranges, too. // Go one out to find abutting ranges, too.
D prev = getPreviousOrSame(lower); 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> public static abstract class AddressRangeMapSetter<E, V>
extends RangeMapSetter<E, Address, AddressRange, V> { extends RangeMapSetter<E, Address, AddressRange, V> {
@Override @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> public static abstract class LifespanMapSetter<E, V>
extends RangeMapSetter<E, Long, Range<Long>, 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) { public static long lowerEndpoint(Range<Long> range) {
if (!range.hasLowerBound()) { if (!range.hasLowerBound()) {
return Long.MIN_VALUE; return Long.MIN_VALUE;
@ -468,6 +623,16 @@ public enum DBTraceUtils {
return range.lowerEndpoint().longValue() + 1; 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) { public static long upperEndpoint(Range<Long> range) {
if (!range.hasUpperBound()) { if (!range.hasUpperBound()) {
return Long.MAX_VALUE; return Long.MAX_VALUE;
@ -478,6 +643,13 @@ public enum DBTraceUtils {
return range.upperEndpoint().longValue() - 1; 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) { public static Range<Long> toRange(long lowerEndpoint, long upperEndpoint) {
if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) { if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) {
return Range.all(); return Range.all();
@ -491,10 +663,27 @@ public enum DBTraceUtils {
return Range.closed(lowerEndpoint, upperEndpoint); 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) { public static Range<Long> toRange(long snap) {
return toRange(snap, Long.MAX_VALUE); 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) { 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 // 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, // 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(); 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) { public static boolean isScratch(long snap) {
return snap < 0; 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) { public static <C extends Comparable<C>> int compareRanges(Range<C> a, Range<C> b) {
int result; int result;
if (!a.hasLowerBound() && b.hasLowerBound()) { if (!a.hasLowerBound() && b.hasLowerBound()) {
@ -548,6 +772,15 @@ public enum DBTraceUtils {
return 0; 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, public static String tableName(String baseName, AddressSpace space, long threadKey,
int frameLevel) { int frameLevel) {
if (space.isRegisterSpace()) { if (space.isRegisterSpace()) {
@ -560,15 +793,17 @@ public enum DBTraceUtils {
} }
/** /**
* TODO: Document me * Truncate or delete an entry to make room
* *
* <p> * <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 data the entry subject to truncation or deletion
* @param span * @param span the span to clear up
* @param lifespanSetter * @param lifespanSetter the method used to truncate the entry
* @param deleter * @param deleter the method used to delete the entry
*/ */
public static <DR extends AbstractDBTraceAddressSnapRangePropertyMapData<?>> void makeWay( public static <DR extends AbstractDBTraceAddressSnapRangePropertyMapData<?>> void makeWay(
DR data, Range<Long> span, BiConsumer<? super DR, Range<Long>> lifespanSetter, 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)); 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) { public static List<Range<Long>> subtract(Range<Long> a, Range<Long> b) {
RangeSet<Long> set = TreeRangeSet.create(); RangeSet<Long> set = TreeRangeSet.create();
set.add(a); set.add(a);
@ -592,12 +834,25 @@ public enum DBTraceUtils {
.collect(Collectors.toList()); .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") @SuppressWarnings("unchecked")
public static <T> Iterator<T> covariantIterator(Iterator<? extends T> it) { public static <T> Iterator<T> covariantIterator(Iterator<? extends T> it) {
// Iterators only support read and remove, not insert. Safe to cast. // Iterators only support read and remove, not insert. Safe to cast.
return (Iterator<T>) it; 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) { public static Iterator<Long> iterateSpan(Range<Long> span) {
return new Iterator<>() { return new Iterator<>() {
final long end = upperEndpoint(span); 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, public static AddressSetView getAddressSet(AddressFactory factory, Address start,
boolean forward) { boolean forward) {
AddressSet all = factory.getAddressSet(); AddressSet all = factory.getAddressSet();
@ -628,6 +894,14 @@ public enum DBTraceUtils {
return factory.getAddressSet(min, start); 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) { public static AddressRange toRange(Address min, Address max) {
if (min.compareTo(max) > 0) { if (min.compareTo(max) > 0) {
throw new IllegalArgumentException("min must precede max"); throw new IllegalArgumentException("min must precede max");

View file

@ -206,17 +206,16 @@ public class DBTraceDataSettingsAdapter
} }
@Override @Override
protected DBTraceAddressSnapRangePropertyMapSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createSpace( protected DBTraceDataSettingsSpace createSpace(AddressSpace space, DBTraceSpaceEntry ent)
AddressSpace space, DBTraceSpaceEntry ent) throws VersionException, IOException { throws VersionException, IOException {
return new DBTraceDataSettingsSpace( return new DBTraceDataSettingsSpace(
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
trace.getStoreFactory(), lock, space, dataType, dataFactory); trace.getStoreFactory(), lock, space, dataType, dataFactory);
} }
@Override @Override
protected DBTraceAddressSnapRangePropertyMapRegisterSpace<DBTraceSettingsEntry, DBTraceSettingsEntry> createRegisterSpace( protected DBTraceDataSettingsRegisterSpace createRegisterSpace(AddressSpace space,
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException {
throws VersionException, IOException {
return new DBTraceDataSettingsRegisterSpace( return new DBTraceDataSettingsRegisterSpace(
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), tableName(space, ent.getThreadKey(), ent.getFrameLevel()),
trace.getStoreFactory(), lock, space, thread, ent.getFrameLevel(), dataType, trace.getStoreFactory(), lock, space, thread, ent.getFrameLevel(), dataType,

View file

@ -84,10 +84,10 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
@Override @Override
default <T> void setProperty(String name, Class<T> valueClass, T value) { default <T> void setProperty(String name, Class<T> valueClass, T value) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
TracePropertySetter<T> setter = TracePropertyMap<? super T> map = getTrace().getInternalAddressPropertyManager()
getTrace().getInternalAddressPropertyManager() .getOrCreatePropertyMapSuper(name, valueClass);
.getOrCreatePropertySetter(name, valueClass); TracePropertyMapSpace<? super T> space = map.getPropertyMapSpace(getTraceSpace(), true);
setter.set(getLifespan(), getAddress(), value); space.set(getLifespan(), getAddress(), value);
} }
} }
@ -122,9 +122,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
@Override @Override
default <T> T getProperty(String name, Class<T> valueClass) { default <T> T getProperty(String name, Class<T> valueClass) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
TracePropertyGetter<T> getter = TracePropertyMap<? extends T> map =
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, valueClass); getTrace().getInternalAddressPropertyManager()
return getter.get(getStartSnap(), getAddress()); .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 @Override
default boolean hasProperty(String name) { default boolean hasProperty(String name) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
getTrace().getInternalAddressPropertyManager().getPropertyMap(name); getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
if (map == null) { if (map == null) {
return false; return false;
@ -163,13 +172,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
@Override @Override
default boolean getVoidProperty(String name) { default boolean getVoidProperty(String name) {
// NOTE: Nearly identical to hasProperty, except named property must be Void type // 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())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
TracePropertyGetter<Void> getter = TracePropertyMap<Void> map =
getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, Void.class); getTrace().getInternalAddressPropertyManager().getPropertyMap(name, Void.class);
if (getter == null) { if (map == null) {
return false; 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 @Override
default void removeProperty(String name) { default void removeProperty(String name) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) {
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
getTrace().getInternalAddressPropertyManager().getPropertyMap(name); getTrace().getInternalAddressPropertyManager().getPropertyMap(name);
if (map == null) { if (map == null) {
return; return;
@ -196,7 +210,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter
@Override @Override
default void visitProperty(PropertyVisitor visitor, String propertyName) { default void visitProperty(PropertyVisitor visitor, String propertyName) {
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
getTrace().getInternalAddressPropertyManager().getPropertyMap(propertyName); getTrace().getInternalAddressPropertyManager().getPropertyMap(propertyName);
if (map == null) { if (map == null) {
return; return;

View file

@ -18,26 +18,29 @@ package ghidra.trace.database.map;
import java.io.*; import java.io.*;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import db.*; import db.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange; 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.*;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.annot.*; import ghidra.util.database.annot.*;
import ghidra.util.exception.NotYetImplementedException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -53,10 +56,12 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
dataFactory); dataFactory);
} }
// TODO: These next several methods are repeated thrice in this file....
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) { protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
// TODO: Would rather not rely on implementation knowledge here // 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); makeWay((DR) entry.getKey(), span);
} }
@ -87,12 +92,21 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
} }
@Override @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())) { try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce( for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) { TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span); 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 public static class DBTraceIntPropertyMap
extends AbstractDBTracePropertyMap<Integer, DBTraceIntPropertyMapEntry> { extends AbstractDBTracePropertyMap<Integer, DBTraceIntPropertyMapEntry> {

View file

@ -47,7 +47,7 @@ import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewListing; 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.model.symbol.TraceFunctionSymbol;
import ghidra.trace.util.*; import ghidra.trace.util.*;
import ghidra.util.*; import ghidra.util.*;
@ -361,7 +361,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
// TODO: Other "special" property types // TODO: Other "special" property types
// TODO: Cover this in testing // TODO: Cover this in testing
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
program.trace.getInternalAddressPropertyManager().getPropertyMap(property); program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
if (map == null) { if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator()); return new WrappingCodeUnitIterator(Collections.emptyIterator());
@ -383,7 +383,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
// TODO: Other "special" property types // TODO: Other "special" property types
// TODO: Cover this in testing // TODO: Cover this in testing
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
program.trace.getInternalAddressPropertyManager().getPropertyMap(property); program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
if (map == null) { if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator()); return new WrappingCodeUnitIterator(Collections.emptyIterator());
@ -406,7 +406,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
// TODO: Other "special" property types // TODO: Other "special" property types
// TODO: Cover this in testing // TODO: Cover this in testing
TracePropertyMap<?> map = TracePropertyMapOperations<?> map =
program.trace.getInternalAddressPropertyManager().getPropertyMap(property); program.trace.getInternalAddressPropertyManager().getPropertyMap(property);
if (map == null) { if (map == null) {
return new WrappingCodeUnitIterator(Collections.emptyIterator()); return new WrappingCodeUnitIterator(Collections.emptyIterator());

View file

@ -17,112 +17,435 @@ package ghidra.trace.database.program;
import java.util.Iterator; 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.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.Saveable;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.*;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.prop.PropertyVisitor;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager { public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager {
protected final DBTraceProgramView program; 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) { public DBTraceProgramViewPropertyMapManager(DBTraceProgramView program) {
this.program = program; this.program = program;
} }
@Override @Override
public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException { public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException {
// TODO Auto-generated method stub return new DBTraceProgramViewIntPropertyMap(program.trace.getAddressPropertyManager()
return null; .createPropertyMap(propertyName, Integer.class),
propertyName);
} }
@Override @Override
public LongPropertyMap createLongPropertyMap(String propertyName) public LongPropertyMap createLongPropertyMap(String propertyName)
throws DuplicateNameException { throws DuplicateNameException {
// TODO Auto-generated method stub return new DBTraceProgramViewLongPropertyMap(program.trace.getAddressPropertyManager()
return null; .createPropertyMap(propertyName, Long.class),
propertyName);
} }
@Override @Override
public StringPropertyMap createStringPropertyMap(String propertyName) public StringPropertyMap createStringPropertyMap(String propertyName)
throws DuplicateNameException { throws DuplicateNameException {
// TODO Auto-generated method stub return new DBTraceProgramViewStringPropertyMap(program.trace.getAddressPropertyManager()
return null; .createPropertyMap(propertyName, String.class),
propertyName);
} }
@Override @Override
public ObjectPropertyMap createObjectPropertyMap(String propertyName, public ObjectPropertyMap createObjectPropertyMap(String propertyName,
Class<? extends Saveable> objectClass) throws DuplicateNameException { Class<? extends Saveable> objectClass) throws DuplicateNameException {
// TODO Auto-generated method stub return new DBTraceProgramViewObjectPropertyMap<>(program.trace.getAddressPropertyManager()
return null; .createPropertyMap(propertyName, objectClass),
propertyName);
} }
@Override @Override
public VoidPropertyMap createVoidPropertyMap(String propertyName) public VoidPropertyMap createVoidPropertyMap(String propertyName)
throws DuplicateNameException { throws DuplicateNameException {
// TODO Auto-generated method stub return new DBTraceProgramViewVoidPropertyMap(program.trace.getAddressPropertyManager()
return null; .createPropertyMap(propertyName, Void.class),
propertyName);
} }
@Override @Override
@SuppressWarnings("unchecked")
public PropertyMap getPropertyMap(String propertyName) { public PropertyMap getPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<?> map =
return null; 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 @Override
public IntPropertyMap getIntPropertyMap(String propertyName) { public IntPropertyMap getIntPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<Integer> map = program.trace.getAddressPropertyManager()
return null; .getPropertyMap(propertyName, Integer.class);
return map == null ? null : new DBTraceProgramViewIntPropertyMap(map, propertyName);
} }
@Override @Override
public LongPropertyMap getLongPropertyMap(String propertyName) { public LongPropertyMap getLongPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<Long> map = program.trace.getAddressPropertyManager()
return null; .getPropertyMap(propertyName, Long.class);
return map == null ? null : new DBTraceProgramViewLongPropertyMap(map, propertyName);
} }
@Override @Override
public StringPropertyMap getStringPropertyMap(String propertyName) { public StringPropertyMap getStringPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<String> map = program.trace.getAddressPropertyManager()
return null; .getPropertyMap(propertyName, String.class);
return map == null ? null : new DBTraceProgramViewStringPropertyMap(map, propertyName);
} }
@Override @Override
@SuppressWarnings("unchecked")
public ObjectPropertyMap getObjectPropertyMap(String propertyName) { public ObjectPropertyMap getObjectPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<?> map =
return null; 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 @Override
public VoidPropertyMap getVoidPropertyMap(String propertyName) { public VoidPropertyMap getVoidPropertyMap(String propertyName) {
// TODO Auto-generated method stub TracePropertyMap<Void> map = program.trace.getAddressPropertyManager()
return null; .getPropertyMap(propertyName, Void.class);
return map == null ? null : new DBTraceProgramViewVoidPropertyMap(map, propertyName);
} }
@Override @Override
public boolean removePropertyMap(String propertyName) { public boolean removePropertyMap(String propertyName) {
// TODO Auto-generated method stub // It would delete for entire trace, not just this view
return false; throw new UnsupportedOperationException();
} }
@Override @Override
public Iterator<String> propertyManagers() { public Iterator<String> propertyManagers() {
// TODO Auto-generated method stub return program.trace.getAddressPropertyManager().getAllProperties().keySet().iterator();
return null; }
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 @Override
public void removeAll(Address addr) { public void removeAll(Address addr) {
// TODO Auto-generated method stub removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), new AddressRangeImpl(addr, addr));
} }
@Override @Override
public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor) public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
// TODO Auto-generated method stub removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap),
new AddressRangeImpl(startAddr, endAddr));
} }
} }

View file

@ -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.map.AbstractDBTracePropertyMap.*; import ghidra.trace.database.map.AbstractDBTracePropertyMap.*;
import ghidra.trace.database.thread.DBTraceThreadManager; 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.*;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.annot.*; 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 @Override
public <T> AbstractDBTracePropertyMap<T, ?> getOrCreatePropertyMap(String name, public <T> AbstractDBTracePropertyMap<T, ?> getOrCreatePropertyMap(String name,
Class<T> valueClass) { Class<T> valueClass) {
@ -216,23 +234,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> TracePropertyGetter<T> getPropertyGetter(String name, Class<T> valueClass) { public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
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,
Class<T> valueClass) { Class<T> valueClass) {
try (LockHold hold = LockHold.lock(lock.writeLock())) { try (LockHold hold = LockHold.lock(lock.writeLock())) {
AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name); AbstractDBTracePropertyMap<?, ?> map = propertyMapsByName.get(name);
@ -248,7 +250,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage
throw new TypeMismatchException("Property " + name + " has type " + throw new TypeMismatchException("Property " + name + " has type " +
map.getValueClass() + ", which is not a super-type of " + valueClass); map.getValueClass() + ", which is not a super-type of " + valueClass);
} }
return (TracePropertyMap<T>) map; return (TracePropertyMap<? super T>) map;
} }
} }

View file

@ -19,7 +19,8 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors; 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; import ghidra.util.exception.DuplicateNameException;
class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManager { class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManager {
@ -43,20 +44,21 @@ class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManage
} }
@Override @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); return internalView.getOrCreatePropertyMap(API_PREFIX + name, valueClass);
} }
@Override @Override
public <T> TracePropertyGetter<T> getPropertyGetter(String name, public <T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name,
Class<T> valueClass) { Class<T> valueClass) {
return internalView.getPropertyGetter(API_PREFIX + name, valueClass); return internalView.getOrCreatePropertyMapSuper(API_PREFIX + name, valueClass);
}
@Override
public <T> TracePropertySetter<T> getOrCreatePropertySetter(String name,
Class<T> valueClass) {
return internalView.getOrCreatePropertySetter(API_PREFIX + name, valueClass);
} }
@Override @Override

View file

@ -21,6 +21,13 @@ import ghidra.program.model.util.TypeMismatchException;
import ghidra.util.Saveable; import ghidra.util.Saveable;
import ghidra.util.exception.DuplicateNameException; 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 { public interface TraceAddressPropertyManager {
/** /**
* Create a property map with the given name having the given type * 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); <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 * 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); <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 * <p>
* @param valueClass the expected type of values to get * If the map already exists, then its values' type must be a super type of that given.
* @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
* *
* @see #createPropertyMap(String, Class) * @see #getOrCreatePropertyMap(String, Class)
* @param name the name * @param name the name
* @param valueClass the expected type of values to set * @param valueClass the expected type of values
* @return the property map suitable for setting values of the given type * @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. * Get the property map with the given name.
@ -94,8 +105,8 @@ public interface TraceAddressPropertyManager {
* <p> * <p>
* Note that no type checking is performed (there is no {@code valueClass} parameter). Thus, the * 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 * 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()} * caller may perform run-time type checking via the
* method. * {@link TracePropertyMapOperations#getValueClass()} method.
* *
* @param name the name * @param name the name
* @return the property map * @return the property map

View file

@ -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);
}

View file

@ -15,34 +15,82 @@
*/ */
package ghidra.trace.model.property; package ghidra.trace.model.property;
import java.util.Map; import ghidra.program.model.address.AddressSpace;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.program.model.address.Address; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.Trace; import ghidra.trace.util.TraceAddressSpace;
import ghidra.trace.model.TraceAddressSnapRange;
/** /**
* 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 TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space, boolean createIfAbsent);
Class<T> getValueClass();
/** /**
* 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> * <p>
* Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in * The property can be re-created with the same or different value type.
* {@link TracePropertyGetter}.
*
* @param snap the snap
* @param address the address
* @return the entry, which includes the ranges and the value
*/ */
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