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

View file

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

View file

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

View file

@ -29,16 +29,17 @@ import ghidra.program.model.pcode.Varnode;
* A userop library for the emulator
*
* <p>
* If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These
* libraries allow you to implement userop, including those declared by the language. Without these,
* the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also
* define new userops, which can be invoked from Sleigh code injected into the emulator.
* If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries
* allow you to implement userops, including those declared by the language. Without these, the
* emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define
* new userops, which can be invoked from Sleigh code injected into the emulator.
*
* <p>
* These libraries can have both Java-callback and p-code implementations of userops. If only using
* p-code implementations, the library can be parameterized with type {@code <T>} and just pass that
* over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes
* concrete bytes, we will fix the library's type to {@code byte[]}.
* concrete bytes, we will fix the library's type to {@code byte[]}. With careful use of the
* {@link PcodeArithmetic}, you can keep the type an abstract {@code <T>} with Java callbacks.
*
* <p>
* Methods in this class (not including those in its nested classes) are implemented as Java
@ -74,8 +75,7 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]>
* @return the length of the string in bytes
*/
@PcodeUserop
public byte[] print_utf8(@OpState PcodeExecutorStatePiece<byte[], byte[]> state,
byte[] start) {
public byte[] print_utf8(@OpState PcodeExecutorState<byte[]> state, byte[] start) {
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
long end = offset;
while (state.getVar(space, end, 1, true)[0] != 0) {

View file

@ -23,6 +23,7 @@ import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace;
@ -41,7 +42,8 @@ import ghidra.program.model.listing.Program;
* call libraries typically implement that interface by annotating p-code userops with
* {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured
* Sleigh. Conventionally, the Java method names of system calls should be
* <em>platform</em>_<em>name</em>. This is to prevent name-space pollution of userops.
* <em>platform</em>_<em>name</em>. This is to prevent name conflicts among userops when several
* libraries are composed.
*
* <p>
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
@ -53,7 +55,7 @@ import ghidra.program.model.listing.Program;
*
* <p>
* For demonstration, this will implement one from scratch for no particular operating system, but
* it will borrow many conventions from linux-amd64.
* it will borrow many conventions from Linux-amd64.
*/
public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
private final static Charset UTF8 = Charset.forName("utf8");
@ -80,11 +82,11 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
/**
* Because the system call numbering is derived from the "syscall" overlay on OTHER space, a
* program is required. The system call analyzer must be applied to it. The program and its
* compiler spec are also used to derive (what it can of) the system call ABI. Notably, it
* applies the calling convention of the functions placed in syscall overlay. Those parts which
* cannot (yet) be derived from the program are instead implemented as abstract methods of this
* class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* program is required. Use the system call analyzer on your program to populate this space. The
* program and its compiler spec are also used to derive (what it can of) the system call ABI.
* Notably, it applies the calling convention of the functions placed in syscall overlay. Those
* parts which cannot (yet) be derived from the program are instead implemented as abstract
* methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
*
* @param machine the emulator
@ -151,7 +153,7 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
* <p>
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the
* userop name should be prefixed with the platform name, to avoid naming collisions among
* userops.
* composed libraries.
*
* <p>
* For demonstration, we will export this as a system call, though that is not required for
@ -173,8 +175,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
* copy of the arithmetic as a field at library construction time.
*/
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
long strLong = arithmetic.toConcrete(str).longValue();
long endLong = arithmetic.toConcrete(end).longValue();
long strLong = arithmetic.toLong(str, Purpose.LOAD);
long endLong = arithmetic.toLong(end, Purpose.OTHER);
byte[] stringBytes =
machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true);
@ -185,12 +187,17 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
// Second, a Structured Sleigh example
/**
* The nested class for syscall implemented using StructuredSleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare it
* public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()}.
* The nested class for syscalls implemented using Structured Sleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
* class public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()} to provide the processor private access.
*/
public class DemoStructuredPart extends StructuredPart {
/**
* This creates a handle to the "demo_write" p-code userop for use in Structured Sleigh.
* Otherwise, there's no way to refer to the userop. Think of it like a "forward" or
* "external" declaration.
*/
UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
/**

View file

@ -21,8 +21,6 @@
//@menupath
//@toolbar
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
@ -88,7 +86,7 @@ public class StandAloneEmuExampleScript extends GhidraScript {
*/
Address entry = dyn.getAddress(0x00400000);
Assembler asm = Assemblers.getAssembler(language);
CodeBuffer buffer = new CodeBuffer(asm, entry);
AssemblyBuffer buffer = new AssemblyBuffer(asm, entry);
buffer.assemble("MOV RCX, 0xdeadbeef");
Address injectHere = buffer.getNext();
buffer.assemble("MOV RAX, 1");
@ -150,30 +148,4 @@ public class StandAloneEmuExampleScript extends GhidraScript {
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
}
public static class CodeBuffer {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final Assembler asm;
private final Address entry;
public CodeBuffer(Assembler asm, Address entry) {
this.asm = asm;
this.entry = entry;
}
public Address getNext() {
return entry.add(baos.size());
}
public byte[] assemble(String line)
throws AssemblySyntaxException, AssemblySemanticException, IOException {
byte[] bytes = asm.assembleLine(getNext(), line);
baos.write(bytes);
return bytes;
}
public byte[] getBytes() {
return baos.toByteArray();
}
}
}

View file

@ -40,5 +40,11 @@
current address and whose registers are initialized to the register context at the current
address. Optionally, other registers can be initialized via the UI or a script. The new thread
is activated so that stepping actions will affect it by default.</P>
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
<P>This action is always available. It lists emulators available for configuration. Selecting
one will set it as the current emulator. The next time emulation is activated, it will use the
selected emulator.</P>
</BODY>
</HTML>

View file

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

View file

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

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 {
public static final String NAME = "Quick Launch";
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) {
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {
Address address = space.getAddress(

View file

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

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

View file

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

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

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

View file

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

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

View file

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

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

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

View file

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

View file

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

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

View file

@ -19,8 +19,9 @@ import java.math.BigInteger;
import java.util.stream.Stream;
import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
@ -39,36 +40,54 @@ public class UniqueRow {
if (isWrite) {
return READ_WRITE;
}
else {
return READ;
}
return READ;
}
else {
if (isWrite) {
return WRITE;
}
else {
return NONE;
}
if (isWrite) {
return WRITE;
}
return NONE;
}
}
/**
* Putting these related methods, all using a common type, into a nested class allows us to
* introduce {@code <T>}, essentially a "universal type."
*
* @param <T> the type of state from which concrete parts are extracted.
*/
public static class ConcretizedState<T> {
private final PcodeExecutorState<T> state;
private final PcodeArithmetic<T> arithmetic;
public ConcretizedState(PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic) {
this.state = state;
this.arithmetic = arithmetic;
}
public byte[] getBytes(Varnode vn) {
return arithmetic.toConcrete(state.getVar(vn), Purpose.INSPECT);
}
public BigInteger getValue(Varnode vn) {
return arithmetic.toBigInteger(state.getVar(vn), Purpose.INSPECT);
}
}
protected final DebuggerPcodeStepperProvider provider;
protected final Language language;
protected final PcodeExecutorState<byte[]> state;
protected final ConcretizedState<?> state;
protected final Varnode vn;
protected DataType dataType;
public UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
PcodeExecutorState<byte[]> state, Varnode vn) {
public <T> UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic, Varnode vn) {
if (!vn.isUnique()) {
throw new AssertionError("Only uniques allowed in unique table");
}
this.provider = provider;
this.language = language;
this.state = state;
this.state = new ConcretizedState<>(state, arithmetic);
this.vn = vn;
}
@ -105,9 +124,26 @@ public class UniqueRow {
return String.format("$U%x:%d", vn.getOffset(), vn.getSize());
}
// TODO: Pluggable columns to display abstract pieces
/**
* Renders the raw bytes as space-separated hexadecimal-digit pairs, if concrete
*
* <p>
* If the state's concrete piece cannot be extracted by the machine's arithmetic, this simply
* returns {@code "(not concrete)"}.
*
* @return the byte string
*/
public String getBytes() {
// TODO: Could keep value cached?
byte[] bytes = state.getVar(vn);
byte[] bytes;
try {
bytes = state.getBytes(vn);
}
catch (UnsupportedOperationException e) {
return "(not concrete)";
}
if (bytes == null) {
return "??";
}
@ -117,9 +153,18 @@ public class UniqueRow {
return NumericUtilities.convertBytesToString(bytes, " ");
}
/**
* Extract the concrete part of the variable as an unsigned big integer
*
* @return the value, or null if the value cannot be made concrete
*/
public BigInteger getValue() {
byte[] bytes = state.getVar(vn);
return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false);
try {
return state.getValue(vn);
}
catch (UnsupportedOperationException e) {
return null;
}
}
public DataType getDataType() {
@ -135,7 +180,7 @@ public class UniqueRow {
if (dataType == null) {
return "";
}
byte[] bytes = state.getVar(vn);
byte[] bytes = state.getBytes(vn);
if (bytes == null) {
return "??";
}

View file

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

View file

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

View file

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

View file

@ -13,4 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
package ghidra.app.plugin.core.debug.gui.register;
import docking.widgets.table.DynamicTableColumn;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A factory for adding a custom column to the Registers table
*
* <p>
* All discovered factories' columns are automatically added as hidden columns to the Registers
* table.
*/
public interface DebuggerRegisterColumnFactory extends ExtensionPoint {
/**
* Create the column
*
* @return the column
*/
DynamicTableColumn<RegisterRow, ?, ?> create();
}

View file

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

View file

@ -18,30 +18,49 @@ package ghidra.app.plugin.core.debug.gui.register;
import java.math.BigInteger;
import java.util.Objects;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Data;
import ghidra.trace.model.Trace;
import ghidra.util.Msg;
/**
* A row displayed in the registers table of the Debugger
*/
public class RegisterRow {
private final DebuggerRegistersProvider provider;
private boolean favorite;
private final int number;
private final Register register;
public RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
protected RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
this.provider = provider;
this.number = number;
this.register = Objects.requireNonNull(register);
this.favorite = provider.isFavorite(register);
}
/**
* Set whether this register is one of the user's favorites
*
* <p>
* Note: Favorites are memorized on a per-compiler-spec (ABI, almost) basis.
*
* @param favorite true if favorite
*/
public void setFavorite(boolean favorite) {
this.favorite = favorite;
provider.setFavorite(register, favorite);
}
/**
* Check if this register is one of the user's favorites
*
* @return true if favorite
*/
public boolean isFavorite() {
return favorite;
}
@ -55,18 +74,42 @@ public class RegisterRow {
return number;
}
/**
* Get the register
*
* @return the register
*/
public Register getRegister() {
return register;
}
/**
* Get the register's name
*
* @return the name
*/
public String getName() {
return register.getName();
}
/**
* Check if the register can be edited
*
* @return true if editable
*/
public boolean isValueEditable() {
return provider.canWriteRegister(register);
}
/**
* Attempt to set the register's value
*
* <p>
* The edit will be directed according to the tool's current edit mode. See
* {@link DebuggerStateEditingService#getCurrentMode(Trace)}
*
* @param value the value
*/
public void setValue(BigInteger value) {
try {
provider.writeRegisterValue(register, value);
@ -78,8 +121,13 @@ public class RegisterRow {
}
/**
* Get the value of the register
*
* <p>
* TODO: Perhaps some caching for all these getters which rely on the DB, since they could be
* invoked on every repaint.
*
* @return the value
*/
public BigInteger getValue() {
return provider.getRegisterValue(register);
@ -89,31 +137,78 @@ public class RegisterRow {
return provider.getRegisterData(register);
}
/**
* Assign a data type to the register
*
* <p>
* This is memorized in the trace for the current and future snaps
*
* @param dataType the data type
*/
public void setDataType(DataType dataType) {
provider.writeRegisterDataType(register, dataType);
}
/**
* Get the data type of the register
*
* @return the data type
*/
public DataType getDataType() {
return provider.getRegisterDataType(register);
}
/**
* Set the value of the register as represented by its data type
*
* @param representation the value to set
*/
public void setRepresentation(String representation) {
provider.writeRegisterValueRepresentation(register, representation);
}
/**
* Check if the register's value can be set via its data type's representation
*
* @return
*/
public boolean isRepresentationEditable() {
return provider.canWriteRegisterRepresentation(register);
}
/**
* Get the value of the register as represented by its data type
*
* @return the value
*/
public String getRepresentation() {
return provider.getRegisterValueRepresentation(register);
}
/**
* Check if the register's value is (completely) known
*
* @return true if known
*/
public boolean isKnown() {
return provider.isRegisterKnown(register);
}
/**
* Check if the register's value changed since last navigation or command
*
* @return true if changed
*/
public boolean isChanged() {
return provider.isRegisterChanged(register);
}
/**
* Get the table's current coordinates (usually also the tool's)
*
* @return the coordinates
*/
public DebuggerCoordinates getCurrent() {
return provider.getCurrent();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ import java.util.concurrent.*;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
@ -31,8 +31,17 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
public abstract class AbstractReadsTargetPcodeExecutorState
extends TraceCachedWriteBytesPcodeExecutorState {
/**
* An executor state piece that knows to read live state if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the desired state, if they're
* not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to 1
* second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*/
public abstract class AbstractReadsTargetPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
@ -86,30 +95,47 @@ public abstract class AbstractReadsTargetPcodeExecutorState
protected final TraceRecorder recorder;
protected final PluginTool tool;
public AbstractReadsTargetPcodeExecutorState(PluginTool tool, Trace trace, long snap,
public AbstractReadsTargetPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(trace, snap, thread, frame);
this.tool = tool;
this.recorder = recorder;
}
protected abstract AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms);
/**
* Get the tool that manages this state's emulator.
*
* <p>
* This is necessary to obtain the static mapping service, in case memory should be filled from
* static images.
*
* @return the tool
*/
public PluginTool getTool() {
return tool;
}
@Override
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> {
TraceMemorySpace tms;
if (s.isUniqueSpace()) {
tms = null;
/**
* Get the recorder associated with the trace
*
* @return this is used to check for and perform live reads
*/
public TraceRecorder getRecorder() {
return recorder;
}
/**
* A partially implemented space map which retrieves "backing" objects from the trace's memory
* and register spaces.
*/
protected abstract class TargetBackedSpaceMap
extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space")) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
}
else {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space")) {
tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true);
}
}
return createCachedSpace(s, tms);
});
}
}
}

View file

@ -17,67 +17,70 @@ package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.BytesPcodeThread;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.trace.TracePcodeEmulator;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.pcode.emu.*;
import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A trace emulator that knows how to read target memory when necessary
*
* <p>
* This is the default emulator used by the Debugger UI to perform interpolation and extrapolation.
* For standalone scripting, consider using {@link BytesTracePcodeEmulator} or {@link PcodeEmulator}
* instead. The former readily reads and records its state to traces, while the latter is the
* simplest use case. See scripts ending in {@code EmuExampleScript} for example uses.
*
* <p>
* This emulator must always be run in its own thread, or at least a thread that can never lock the
* UI. It blocks on target reads so that execution can proceed synchronously. Probably the most
* suitable option is to use a background task.
*/
public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator
implements DebuggerPcodeMachine<byte[]> {
protected final PluginTool tool;
protected final TraceRecorder recorder;
public DebuggerTracePcodeEmulator(PluginTool tool, Trace trace, long snap,
/**
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the trace from which the emulator loads state
* @param snap the snap from which the emulator loads state
* @param recorder if applicable, the recorder for the trace's live target
*/
public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
super(trace, snap);
this.tool = tool;
this.recorder = recorder;
}
protected boolean isRegisterKnown(String threadName, Register register) {
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName);
TraceMemoryRegisterSpace space =
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
if (space == null) {
return false;
}
return space.getState(snap, register) == TraceMemoryState.KNOWN;
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
protected BytesPcodeThread createThread(String name) {
BytesPcodeThread thread = super.createThread(name);
Register contextreg = language.getContextBaseRegister();
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) {
RegisterValue context = trace.getRegisterContextManager()
.getValueWithDefault(language, contextreg, snap, thread.getCounter());
if (context != null) { // TODO: Why does this happen?
thread.overrideContext(context);
}
}
initializeThreadContext(thread);
return thread;
}
@Override
protected PcodeExecutorState<byte[]> createSharedState() {
public TracePcodeExecutorState<byte[]> createSharedState() {
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder);
}
@Override
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,

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.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.Range;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent;
@ -32,8 +37,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*;
@ -49,6 +53,7 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.CompareResult;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
@ -74,20 +79,21 @@ import ghidra.util.task.TaskMonitor;
})
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
protected static final int MAX_CACHE_SIZE = 5;
protected static long nextSnap = Long.MIN_VALUE; // HACK
protected static class CacheKey implements Comparable<CacheKey> {
protected final Trace trace;
protected final TraceSchedule time;
private final int hashCode;
public CacheKey(Trace trace, TraceSchedule time) {
this.trace = trace;
this.time = time;
this.trace = Objects.requireNonNull(trace);
this.time = Objects.requireNonNull(time);
this.hashCode = Objects.hash(trace, time);
}
@Override
public int hashCode() {
return Objects.hash(trace, time);
return hashCode;
}
@Override
@ -130,9 +136,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
protected static class CachedEmulator {
final DebuggerTracePcodeEmulator emulator;
final DebuggerPcodeMachine<?> emulator;
public CachedEmulator(DebuggerTracePcodeEmulator emulator) {
public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
this.emulator = emulator;
}
}
@ -162,6 +168,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
protected DebuggerPcodeEmulatorFactory emulatorFactory =
new BytesDebuggerPcodeEmulatorFactory();
protected final Set<CacheKey> eldest = new LinkedHashSet<>();
protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>();
protected final AsyncLazyMap<CacheKey, Long> requests =
@ -180,6 +189,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
DockingAction actionEmulateProgram;
DockingAction actionEmulateAddThread;
Map<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> //
actionsChooseEmulatorFactory = new HashMap<>();
final ChangeListener classChangeListener = this::classesChanged;
public DebuggerEmulationServicePlugin(PluginTool tool) {
super(tool);
@ -205,6 +218,46 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
.popupWhen(this::emulateAddThreadEnabled)
.onAction(this::emulateAddThreadActivated)
.buildAndInstall(tool);
ClassSearcher.addChangeListener(classChangeListener);
updateConfigureEmulatorStates();
}
private void classesChanged(ChangeEvent e) {
updateConfigureEmulatorStates();
}
private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) {
ToggleDockingAction action = ConfigureEmulatorAction.builder(this)
.menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle())
.onAction(ctx -> configureEmulatorActivated(factory))
.buildAndInstall(tool);
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), "zz");
return action;
}
private void updateConfigureEmulatorStates() {
Map<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> byClass =
getEmulatorFactories().stream()
.collect(Collectors.toMap(DebuggerPcodeEmulatorFactory::getClass,
Objects::requireNonNull));
Iterator<Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction>> it =
actionsChooseEmulatorFactory.entrySet().iterator();
while (it.hasNext()) {
Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> ent =
it.next();
if (!byClass.keySet().contains(ent.getKey())) {
tool.removeAction(ent.getValue());
}
}
for (Entry<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> ent : byClass
.entrySet()) {
if (!actionsChooseEmulatorFactory.containsKey(ent.getKey())) {
ToggleDockingAction action = createActionChooseEmulator(ent.getValue());
action.setSelected(ent.getKey() == emulatorFactory.getClass());
actionsChooseEmulatorFactory.put(ent.getKey(), action);
}
}
}
private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) {
@ -228,7 +281,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = null;
try {
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
}
@ -275,7 +327,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
private void emulateAddThreadActivated(ProgramLocationActionContext ctx) {
Program programOrView = ctx.getProgram();
if (programOrView instanceof TraceProgramView) {
TraceProgramView view = (TraceProgramView) programOrView;
@ -322,6 +373,35 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) {
// TODO: Pull up config page. Tool Options? Program/Trace Options?
setEmulatorFactory(factory);
}
@Override
public Collection<DebuggerPcodeEmulatorFactory> getEmulatorFactories() {
return ClassSearcher.getInstances(DebuggerPcodeEmulatorFactory.class);
}
@Override
public synchronized void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory) {
emulatorFactory = Objects.requireNonNull(factory);
for (ToggleDockingAction toggle : actionsChooseEmulatorFactory.values()) {
toggle.setSelected(false);
}
ToggleDockingAction chosen = actionsChooseEmulatorFactory.get(factory.getClass());
if (chosen == null) {
// Must be special or otherwise not discovered. Could happen.
Msg.warn(this, "An undiscovered emulator factory was set via the API: " + factory);
}
chosen.setSelected(true);
}
@Override
public synchronized DebuggerPcodeEmulatorFactory getEmulatorFactory() {
return emulatorFactory;
}
protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
synchronized (cache) {
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
@ -378,7 +458,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = key.trace;
TraceSchedule time = key.time;
CachedEmulator ce;
DebuggerTracePcodeEmulator emu;
DebuggerPcodeMachine<?> emu;
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
if (ancestor != null) {
CacheKey prevKey = ancestor.getKey();
@ -396,7 +476,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
time.finish(trace, prevKey.time, emu, monitor);
}
else {
emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(),
emu = emulatorFactory.create(tool, trace, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace));
ce = new CachedEmulator(emu);
monitor.initialize(time.totalTickCount());
@ -405,7 +485,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
TraceSnapshot destSnap;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
destSnap = findScratch(trace, time);
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
emu.writeDown(trace, destSnap.getKey(), time.getSnap());
}
synchronized (cache) {
@ -436,7 +516,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
@Override
public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) {
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
CachedEmulator ce = cache.get(new CacheKey(trace, time));
return ce == null ? null : ce.emulator;
}

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;
import java.util.Collection;
import java.util.Map.Entry;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
public class ReadsTargetMemoryPcodeExecutorState
extends AbstractReadsTargetPcodeExecutorState {
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
AddressSet unknown;
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (fillUnknownWithRecorder(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (fillUnknownWithStaticImages(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
if (!isLive()) {
return false;
}
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true;
}
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, unknown, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange srng = mappedRng.getSourceAddressRange();
long shift = mappedRng.getShift();
for (AddressRange subsrng : initialized.intersectRange(srng.getMinAddress(),
srng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subsrng);
long lower = subsrng.getMinAddress().getOffset();
long fullLen = subsrng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
int read =
memory.getBytes(space.getAddress(lower), data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subsrng + ". Got " + read +
" bytes");
}
// write(lower - shift, data, 0 ,read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
}
/**
* A state composing a single {@link ReadsTargetMemoryPcodeExecutorStatePiece}
*/
public class ReadsTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread probably null, since this the shared part
* @param frame probably 0, because frame only matters for non-null thread
* @param recorder the recorder of the emulator
*/
public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetMemoryCachedSpace(language, s, tms, snap);
super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}

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;
import java.util.HashSet;
import java.util.Set;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
public class ReadsTargetRegistersPcodeExecutorState
extends AbstractReadsTargetPcodeExecutorState {
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
Set<Register> toRead = new HashSet<>();
for (AddressRange rng : unknown) {
Register register =
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + rng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
}
}
/**
* A state composing a single {@link ReadsTargetRegistersPcodeExecutorStatePiece}
*/
public class ReadsTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread the thread to which the state is assigned
* @param frame the frame to which the state is assigned, probably 0
* @param recorder the recorder of the emulator
*/
public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetRegistersCachedSpace(language, s, tms, snap);
super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}

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 ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders();
protected final DebuggerSelectMappingOfferDialog offerDialog =
new DebuggerSelectMappingOfferDialog();
protected final DebuggerSelectMappingOfferDialog offerDialog;
protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog();
DockingAction actionDisconnectAll;
@ -218,7 +217,7 @@ public class DebuggerModelServicePlugin extends Plugin
public DebuggerModelServicePlugin(PluginTool tool) {
super(tool);
offerDialog = new DebuggerSelectMappingOfferDialog(tool);
ClassSearcher.addChangeListener(classChangeListener);
refreshFactoryInstances();
connectDialog.setModelService(this);

View file

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

View file

@ -21,6 +21,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableCo
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Swing;
public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, K, R, T>
@ -28,9 +29,10 @@ public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & E
AsyncDebouncer<Void> debouncer = new AsyncDebouncer<Void>(AsyncTimer.DEFAULT_TIMER, 100);
public DebouncedRowWrappedEnumeratedColumnTableModel(String name, Class<C> colType,
public DebouncedRowWrappedEnumeratedColumnTableModel(PluginTool tool, String name,
Class<C> colType,
Function<T, K> keyFunc, Function<T, R> wrapper) {
super(name, colType, keyFunc, wrapper);
super(tool, name, colType, keyFunc, wrapper);
debouncer.addListener(this::settled);
}

View file

@ -15,19 +15,59 @@
*/
package ghidra.app.services;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A service for accessing managed emulators.
*
* <p>
* Managed emulators are employed by the UI and trace manager to perform emulation requested by the
* user. Scripts may interact with these managed emulators, or they may instantiate their own
* unmanaged emulators, without using this service.
*/
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
public interface DebuggerEmulationService {
/**
* Get the available emulator factories
*
* @return the collection of factories
*/
Collection<DebuggerPcodeEmulatorFactory> getEmulatorFactories();
/**
* Set the current emulator factory
*
* <p>
* TODO: Should this be set on a per-program, per-trace basis? Need to decide what is saved to
* the tool and what is saved to the program/trace. My inclination is to save current factory to
* the tool, but the config options for each factory to the program/trace.
*
* <p>
* TODO: Should there be some opinion service for choosing default configs? Seem overly
* complicated for what it offers. For now, we won't save anything, we'll default to the
* (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration
* options.
*
* @param factory the chosen factory
*/
void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory);
/**
* Get the current emulator factory
*
* @return the factory
*/
DebuggerPcodeEmulatorFactory getEmulatorFactory();
/**
* Perform emulation to realize the machine state of the given time coordinates
*
@ -81,5 +121,5 @@ public interface DebuggerEmulationService {
* @param time the time coordinates, including initial snap, steps, and p-code steps
* @return the copied p-code frame
*/
DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time);
DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time);
}

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
@ -36,12 +37,19 @@ import ghidra.program.model.pcode.Varnode;
* until the computation has been performed -- assuming the requested variable actually depends on
* that computation.
*
* <p>
* TODO: Deprecate this? It's clever, but it'd probably be easier to just use a synchronous executor
* on a separate thread. The necessity of {@link #stepAsync(PcodeFrame, PcodeUseropLibrary)}, etc.,
* indicates a failure of the interface to encapsulate this use case. We can adjust the interface,
* which would probably not end well, or we can continue to allow the CompletableFuture-specific
* steppers to leak out, or we can just torch this and use another thread.
*
* @param <T> the type of values in the state
*/
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
public AsyncPcodeExecutor(SleighLanguage language,
PcodeArithmetic<CompletableFuture<T>> arithmetic,
PcodeExecutorStatePiece<CompletableFuture<T>, CompletableFuture<T>> state) {
PcodeExecutorState<CompletableFuture<T>> state) {
super(language, arithmetic, state);
}
@ -73,7 +81,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
Varnode condVar = op.getInput(1);
CompletableFuture<T> cond = state.getVar(condVar);
return cond.thenAccept(c -> {
if (arithmetic.isTrue(cond)) {
if (arithmetic.isTrue(cond, Purpose.CONDITION)) {
executeBranch(op, frame);
}
});

View file

@ -15,21 +15,23 @@
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.lang.Language;
/**
* An arithmetic which can operate on futures of a wrapped type
*
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
* @param <T> the type of values wrapped
*/
public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<CompletableFuture<T>> {
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_BE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN);
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_LE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN);
@Deprecated(forRemoval = true) // TODO: Not getting used
public static final AsyncWrappedPcodeArithmetic<BigInteger> BIGINT =
new AsyncWrappedPcodeArithmetic<>(BigIntegerPcodeArithmetic.INSTANCE);
public static AsyncWrappedPcodeArithmetic<byte[]> forEndian(boolean isBigEndian) {
return isBigEndian ? BYTES_BE : BYTES_LE;
@ -46,46 +48,71 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
}
@Override
public CompletableFuture<T> unaryOp(UnaryOpBehavior op, int sizeout, int sizein1,
CompletableFuture<T> in1) {
return in1.thenApply(t1 -> arithmetic.unaryOp(op, sizeout, sizein1, t1));
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
AsyncWrappedPcodeArithmetic<?> that = (AsyncWrappedPcodeArithmetic<?>) obj;
return Objects.equals(this.arithmetic, that.arithmetic);
}
@Override
public CompletableFuture<T> binaryOp(BinaryOpBehavior op, int sizeout, int sizein1,
public Endian getEndian() {
return arithmetic.getEndian();
}
@Override
public CompletableFuture<T> unaryOp(int opcode, int sizeout, int sizein1,
CompletableFuture<T> in1) {
return in1.thenApply(t1 -> arithmetic.unaryOp(opcode, sizeout, sizein1, t1));
}
@Override
public CompletableFuture<T> binaryOp(int opcode, int sizeout, int sizein1,
CompletableFuture<T> in1, int sizein2, CompletableFuture<T> in2) {
return in1.thenCombine(in2,
(t1, t2) -> arithmetic.binaryOp(op, sizeout, sizein1, t1, sizein2, t2));
(t1, t2) -> arithmetic.binaryOp(opcode, sizeout, sizein1, t1, sizein2, t2));
}
@Override
public CompletableFuture<T> fromConst(long value, int size) {
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size));
public CompletableFuture<T> modBeforeStore(int sizeout, int sizeinAddress,
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
}
@Override
public CompletableFuture<T> fromConst(BigInteger value, int size, boolean isContextreg) {
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size, isContextreg));
public CompletableFuture<T> modAfterLoad(int sizeout, int sizeinAddress,
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
}
@Override
public boolean isTrue(CompletableFuture<T> cond) {
if (!cond.isDone()) {
throw new AssertionError("You need a better 8-ball");
public CompletableFuture<T> fromConst(byte[] value) {
return CompletableFuture.completedFuture(arithmetic.fromConst(value));
}
@Override
public byte[] toConcrete(CompletableFuture<T> value, Purpose purpose) {
if (!value.isDone()) {
throw new ConcretionError("You need a better 8-ball", purpose);
}
return arithmetic.isTrue(cond.getNow(null));
return arithmetic.toConcrete(value.getNow(null), purpose);
}
@Override
public BigInteger toConcrete(CompletableFuture<T> cond, boolean isContextreg) {
if (!cond.isDone()) {
throw new AssertionError("You need a better 8-ball");
public long sizeOf(CompletableFuture<T> value) {
if (!value.isDone()) {
// TODO: Make a class which has future and expected size?
throw new RuntimeException("You need a better 8-ball");
}
return arithmetic.toConcrete(cond.getNow(null), isContextreg);
return arithmetic.sizeOf(value.getNow(null));
}
@Override
public CompletableFuture<T> sizeOf(CompletableFuture<T> value) {
return value.thenApply(v -> arithmetic.sizeOf(v));
public CompletableFuture<T> sizeOfAbstract(CompletableFuture<T> value) {
return value.thenApply(v -> arithmetic.sizeOfAbstract(v));
}
}

View file

@ -17,9 +17,14 @@ package ghidra.pcode.exec;
import java.util.concurrent.CompletableFuture;
public class AsyncWrappedPcodeExecutorState<T> extends AsyncWrappedPcodeExecutorStatePiece<T, T>
implements PcodeExecutorState<CompletableFuture<T>> {
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> state) {
super(state);
/**
* The state for a {@link AsyncWrappedPcodeExecutorStatePiece}
*
* @param <T> the type of wrapped values
*/
public class AsyncWrappedPcodeExecutorState<T>
extends DefaultPcodeExecutorState<CompletableFuture<T>> {
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece) {
super(new AsyncWrappedPcodeExecutorStatePiece<>(piece));
}
}

View file

@ -19,17 +19,38 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
/**
* An executor state piece which can operate on futures of a wrapped type
*
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
* @param <T> the type of values wrapped
*/
public class AsyncWrappedPcodeExecutorStatePiece<A, T>
implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> {
protected final PcodeExecutorStatePiece<A, T> state;
protected final AsyncWrappedPcodeArithmetic<A> addressArithmetic;
protected final AsyncWrappedPcodeArithmetic<T> arithmetic;
private CompletableFuture<?> lastWrite = AsyncUtils.NIL;
public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, T> state) {
this.state = state;
this.addressArithmetic = new AsyncWrappedPcodeArithmetic<>(state.getAddressArithmetic());
this.arithmetic = new AsyncWrappedPcodeArithmetic<>(state.getArithmetic());
}
@Override
public AsyncWrappedPcodeArithmetic<A> getAddressArithmetic() {
return addressArithmetic;
}
@Override
public AsyncWrappedPcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
protected boolean isWriteDone() {
@ -45,41 +66,36 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
}
protected CompletableFuture<?> doSetVar(AddressSpace space, CompletableFuture<A> offset,
int size, boolean truncateAddressableUnit, CompletableFuture<T> val) {
int size, boolean quantize, CompletableFuture<T> val) {
return offset.thenCompose(off -> val.thenAccept(v -> {
state.setVar(space, off, size, truncateAddressableUnit, v);
state.setVar(space, off, size, quantize, v);
}));
}
@Override
public void setVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean truncateAddressableUnit, CompletableFuture<T> val) {
nextWrite(() -> doSetVar(space, offset, size, truncateAddressableUnit, val));
boolean quantize, CompletableFuture<T> val) {
nextWrite(() -> doSetVar(space, offset, size, quantize, val));
}
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
int size, boolean truncateAddressableUnit) {
int size, boolean quantize) {
return offset.thenApply(off -> {
return state.getVar(space, off, size, truncateAddressableUnit);
return state.getVar(space, off, size, quantize);
});
}
@Override
public CompletableFuture<T> getVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean truncateAddressableUnit) {
return nextRead(() -> doGetVar(space, offset, size, truncateAddressableUnit));
boolean quantize) {
return nextRead(() -> doGetVar(space, offset, size, quantize));
}
@Override
public CompletableFuture<A> longToOffset(AddressSpace space, long l) {
return CompletableFuture.completedFuture(state.longToOffset(space, l));
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
if (!isWriteDone()) {
throw new AssertionError("An async write is still pending");
}
return state.getConcreteBuffer(address);
return state.getConcreteBuffer(address, purpose);
}
}

View file

@ -19,12 +19,22 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
public enum TracePcodeUtils {
public enum DebuggerPcodeUtils {
;
/**
* Get an executor which can be used to evaluate Sleigh expressions at the given coordinates,
* asynchronously.
*
* <p>
* TODO: Change this to be synchronous and have clients evaluate expressions in another thread?
*
* @param coordinates the coordinates
* @return the executor
*/
public static AsyncPcodeExecutor<byte[]> executorForCoordinates(
DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
@ -39,7 +49,7 @@ public enum TracePcodeUtils {
PcodeExecutorState<CompletableFuture<byte[]>> state;
if (coordinates.getRecorder() == null) {
state = new AsyncWrappedPcodeExecutorState<>(
new TraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame()));
}
else {

View file

@ -15,128 +15,26 @@
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
/**
* A state composing a single {@link TraceRecorderAsyncPcodeExecutorStatePiece}
*/
public class TraceRecorderAsyncPcodeExecutorState
extends AsyncWrappedPcodeExecutorState<byte[]> {
private final TraceRecorder recorder;
private final TraceBytesPcodeExecutorState traceState;
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState;
extends DefaultPcodeExecutorState<CompletableFuture<byte[]>> {
/**
* Create the state
*
* @param recorder the recorder for the trace's live target
* @param snap the user's current snap
* @param thread the user's current thread
* @param frame the user's current frame
*/
public TraceRecorderAsyncPcodeExecutorState(TraceRecorder recorder, long snap,
TraceThread thread, int frame) {
super(new TraceBytesPcodeExecutorState(recorder.getTrace(), snap, thread, frame));
this.recorder = recorder;
this.traceState = (TraceBytesPcodeExecutorState) state;
this.traceMemState =
new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame);
}
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
boolean truncateAddressableUnit, byte[] val) {
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
}
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {
Address floor = map.floorKey(addr);
NavigableMap<Address, byte[]> tail;
if (floor == null) {
tail = map;
}
else {
tail = map.tailMap(floor, true);
}
byte[] result = new byte[size];
for (Map.Entry<Address, byte[]> ent : tail.entrySet()) {
long off = ent.getKey().subtract(addr);
if (off >= size || off < 0) {
break;
}
int subSize = Math.min(size - (int) off, ent.getValue().length);
System.arraycopy(ent.getValue(), 0, result, (int) off, subSize);
}
return result;
}
protected CompletableFuture<byte[]> doGetTargetVar(AddressSpace space, long offset,
int size, boolean truncateAddressableUnit) {
if (space.isMemorySpace()) {
Address addr = space.getAddress(truncateOffset(space, offset));
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
CompletableFuture<NavigableMap<Address, byte[]>> future =
recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
return future.thenApply(map -> {
return knitFromResults(map, addr, size);
});
}
assert space.isRegisterSpace();
Language lang = recorder.getTrace().getBaseLanguage();
Register register = lang.getRegister(space, offset, size);
if (register == null) {
// TODO: Is this too restrictive?
throw new IllegalArgumentException(
"read from register space must be from one register");
}
Register baseRegister = register.getBaseRegister();
CompletableFuture<Map<Register, RegisterValue>> future =
recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(),
Set.of(baseRegister));
return future.thenApply(map -> {
RegisterValue baseVal = map.get(baseRegister);
if (baseVal == null) {
return state.getVar(space, offset, size, truncateAddressableUnit);
}
BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue();
return Utils.bigIntegerToBytes(val, size,
recorder.getTrace().getBaseLanguage().isBigEndian());
});
}
protected boolean isTargetSpace(AddressSpace space) {
return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() &&
!space.isUniqueSpace();
}
@Override
protected CompletableFuture<?> doSetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean truncateAddressableUnit,
CompletableFuture<byte[]> val) {
if (!isTargetSpace(space)) {
return super.doSetVar(space, offset, size, truncateAddressableUnit, val);
}
return offset.thenCompose(off -> val.thenCompose(v -> {
return doSetTargetVar(space, traceState.offsetToLong(off), size,
truncateAddressableUnit, v);
}));
}
@Override
protected CompletableFuture<byte[]> doGetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean truncateAddressableUnit) {
if (!isTargetSpace(space)) {
return super.doGetVar(space, offset, size, truncateAddressableUnit);
}
return offset.thenCompose(off -> {
TraceMemoryState ms = traceMemState.getVar(space, off, size, truncateAddressableUnit);
if (ms == TraceMemoryState.KNOWN) {
return super.doGetVar(space, offset, size, truncateAddressableUnit);
}
return doGetTargetVar(space, traceState.offsetToLong(off), size,
truncateAddressableUnit);
});
super(new TraceRecorderAsyncPcodeExecutorStatePiece(recorder, snap, thread, frame));
}
}

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
public void tearDown() {
waitForTasks();
runSwing(() -> traceManager.setSaveTracesByDefault(false));
runSwing(() -> {
if (traceManager == null) {
return;
}
traceManager.setSaveTracesByDefault(false);
});
if (tb != null) {
if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) {

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

View file

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

View file

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

View file

@ -24,8 +24,17 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
extends TraceCachedWriteBytesPcodeExecutorState {
/**
* A state piece which can check for uninitialized reads
*
* <p>
* Depending on the use case, it may be desirable to ensure all reads through the course of
* emulation are from initialized parts of memory. For traces, there's an additional consideration
* as to whether the values are present, but state. Again, depending on the use case, that may be
* acceptable. See the extensions of this class for "stock" implementations.
*/
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
protected class CheckedCachedSpace extends CachedSpace {
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source,
@ -45,16 +54,31 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
}
}
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
}
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CheckedCachedSpace(language, space, backing, snap);
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TraceBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CheckedCachedSpace(language, space, backing, snap);
}
};
}
/**
* Decide what to do, give that a portion of a read is uninitialized
*
* @param backing the object backing the address space that was read
* @param start the starting address of the requested read
* @param size the size of the requested read
* @param uninitialized the portion of the read that is uninitialized
* @return the adjusted size of the read
* @throws Exception to interrupt the emulator
*/
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized);
}

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.primitives.UnsignedLong;
import ghidra.pcode.exec.AbstractBytesPcodeExecutorState;
import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece;
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
@ -32,22 +32,23 @@ import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
/**
* A state which reads bytes from a trace, but caches writes internally.
* A state piece which reads bytes from a trace, but caches writes internally.
*
* <p>
* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but
* rather are cached in this state. If desired, those cached writes can be written back out at a
* later time.
*/
public class TraceCachedWriteBytesPcodeExecutorState
extends AbstractBytesPcodeExecutorState<TraceMemorySpace, CachedSpace> {
public class BytesTracePcodeExecutorStatePiece
extends AbstractBytesPcodeExecutorStatePiece<CachedSpace>
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
protected final Trace trace;
protected final long snap;
protected final TraceThread thread;
protected final int frame;
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage());
this.trace = trace;
@ -56,7 +57,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
this.frame = frame;
}
public static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
protected static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
protected final long snap;
@ -126,50 +127,69 @@ public class TraceCachedWriteBytesPcodeExecutorState
}
}
/**
* Get the state's source trace
*
* @return the trace
*/
public Trace getTrace() {
return trace;
}
/**
* Get the source snap
*
* @return the snap
*/
public long getSnap() {
return snap;
}
/**
* Get the source thread, if a local state
*
* @return the thread
*/
public TraceThread getThread() {
return thread;
}
/**
* Get the source frame, if a local state
*
* @return the frame, probably 0
*/
public int getFrame() {
return frame;
}
/**
* Write the accumulated writes into the given trace
*
* <p>
* NOTE: This method requires a transaction to have already been started on the destination
* trace.
*
* @param trace the trace to modify
* @param snap the snap within the trace
* @param thread the thread to take register writes
* @param frame the frame for register writes
*/
public void writeCacheDown(Trace trace, long snap, TraceThread thread, int frame) {
@Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
if (trace.getBaseLanguage() != language) {
throw new IllegalArgumentException("Destination trace must be same language as source");
}
for (CachedSpace cached : spaces.values()) {
for (CachedSpace cached : spaceMap.values()) {
cached.writeDown(trace, snap, thread, frame);
}
}
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
/**
* A space map which binds spaces to corresponding spaces in the trace
*/
protected class TraceBackedSpaceMap extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
}
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CachedSpace(language, space, backing, snap);
}
}
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CachedSpace(language, space, backing, snap);
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TraceBackedSpaceMap();
}
}

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 org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
@ -32,8 +32,20 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
public class TraceBytesPcodeExecutorState
extends AbstractLongOffsetPcodeExecutorState<byte[], TraceMemorySpace> {
/**
* An executor state piece that operates directly on trace memory and registers
*
* <p>
* This differs from {@link BytesTracePcodeExecutorStatePiece} in that writes performed by the
* emulator immediately affect the trace. There is no caching. In effect, the trace <em>is</em> the
* state. This is used primarily in testing to initialize trace state using Sleigh, which is more
* succinct than accessing trace memory and registers via the trace API. It may also be incorporated
* into the UI at a later time.
*
* @see TraceSleighUtils
*/
public class DirectBytesTracePcodeExecutorStatePiece
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], TraceMemorySpace> {
protected final SemisparseByteArray unique = new SemisparseByteArray();
private final Trace trace;
@ -43,8 +55,10 @@ public class TraceBytesPcodeExecutorState
private final DefaultTraceTimeViewport viewport;
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
protected DirectBytesTracePcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread,
int frame) {
super(language, arithmetic, arithmetic);
this.trace = trace;
this.snap = snap;
this.thread = thread;
@ -54,48 +68,65 @@ public class TraceBytesPcodeExecutorState
this.viewport.setSnap(snap);
}
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorState<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)) {
@Override
public void setVar(AddressSpace space, Pair<byte[], TraceMemoryState> offset, int size,
boolean truncateAddressableUnit, Pair<byte[], TraceMemoryState> val) {
if (offset.getRight() == TraceMemoryState.KNOWN) {
super.setVar(space, offset, size, truncateAddressableUnit, val);
return;
}
super.setVar(space, offset, size, truncateAddressableUnit,
new ImmutablePair<>(val.getLeft(), TraceMemoryState.UNKNOWN));
}
@Override
public Pair<byte[], TraceMemoryState> getVar(AddressSpace space,
Pair<byte[], TraceMemoryState> offset, int size,
boolean truncateAddressableUnit) {
Pair<byte[], TraceMemoryState> result =
super.getVar(space, offset, size, truncateAddressableUnit);
if (offset.getRight() == TraceMemoryState.KNOWN) {
return result;
}
return new ImmutablePair<>(result.getLeft(), TraceMemoryState.UNKNOWN);
}
};
protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap,
TraceThread thread, int frame) {
this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame);
}
public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
this(trace.getBaseLanguage(), trace, snap, thread, frame);
}
/**
* Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary
* attribute
*
* <p>
* If every part of every input to the expression is {@link TraceMemoryState#KNOWN}, then the
* expression's value will be marked {@link TraceMemoryState#KNOWN}. Otherwise, it's marked
* {@link TraceMemoryState#UNKNOWN}.
*
* @return the paired executor state
*/
public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorStatePiece<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
}
/**
* Get the trace
*
* @return the trace
*/
public Trace getTrace() {
return trace;
}
/**
* Re-bind this state to another snap
*
* @param snap the new snap
*/
public void setSnap(long snap) {
this.snap = snap;
this.viewport.setSnap(snap);
}
/**
* Get the current snap
*
* @return the snap
*/
public long getSnap() {
return snap;
}
/**
* Re-bind this state to another thread
*
* @param thread the new thread
*/
public void setThread(TraceThread thread) {
if (thread != null & thread.getTrace() != trace) {
throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
@ -103,28 +134,33 @@ public class TraceBytesPcodeExecutorState
this.thread = thread;
}
/**
* Get the current thread
*
* @return the thread
*/
public TraceThread getThread() {
return thread;
}
/**
* Re-bind this state to another frame
*
* @param frame the new frame
*/
public void setFrame(int frame) {
this.frame = frame;
}
/**
* Get the current frame
*
* @return the frame
*/
public int getFrame() {
return frame;
}
@Override
public long offsetToLong(byte[] offset) {
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
}
@Override
public byte[] longToOffset(AddressSpace space, long l) {
return arithmetic.fromConst(l, space.getPointerSize());
}
@Override
protected void setUnique(long offset, int size, byte[] val) {
assert size == val.length;
@ -164,7 +200,7 @@ public class TraceBytesPcodeExecutorState
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return trace.getMemoryManager().getBufferAt(snap, address);
}
}

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;
import com.google.common.collect.Range;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece}
*/
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorState {
extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param trace the trace from which to load state
* @param snap the snap from which to load state
* @param thread if applicable, the thread identifying the register space
* @param frame if applicable, the frame identifying the register space
*/
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
}
@Override
protected AddressSetView getKnown(TraceMemorySpace source) {
return source.getAddressesWithState(Range.closed(0L, snap),
s -> s == TraceMemoryState.KNOWN);
}
@Override
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known.");
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
frame));
}
}

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;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState}
*/
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorState {
extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param trace the trace from which to load state
* @param snap the snap from which to load state
* @param thread if applicable, the thread identifying the register space
* @param frame if applicable, the frame identifying the register space
*/
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
}
protected AddressSetView getKnown(TraceMemorySpace source) {
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
}
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown.");
}
@Override
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized) {
if (backing == null) {
if (!uninitialized.contains(start)) {
return (int) uninitialized.getMinAddress().subtract(start);
}
throw excFor(uninitialized);
}
// TODO: Could find first instead?
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
if (unknown.isEmpty()) {
return size;
}
if (!unknown.contains(start)) {
return (int) unknown.getMinAddress().subtract(start);
}
throw excFor(unknown);
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
frame));
}
}

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 ghidra.pcode.exec.ConcretionError;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.lang.Endian;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* The p-code arithmetic for {@link TraceMemoryState}
*
* <p>
* This arithmetic is meant to be used as an auxiliary to a concrete arithmetic. It should be used
* with a state that knows how to load state markings from the same trace as the concrete state, so
* that it can compute the "state" of a Sleigh expression's value. It essentially works like a
* rudimentary taint analyzer: If any part of any input to the expression in tainted, i.e., not
* {@link TraceMemoryState#KNOWN}, then the result is {@link TraceMemoryState#UNKNOWN}. This is best
* exemplified in
* {@link #binaryOp(BinaryOpBehavior, int, int, TraceMemoryState, int, TraceMemoryState)}.
*/
public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemoryState> {
/** The singleton instance */
INSTANCE;
@Override
public TraceMemoryState unaryOp(UnaryOpBehavior op, int sizeout, int sizein1,
public Endian getEndian() {
return null;
}
@Override
public TraceMemoryState unaryOp(int opcode, int sizeout, int sizein1,
TraceMemoryState in1) {
return in1;
}
@Override
public TraceMemoryState binaryOp(BinaryOpBehavior op, int sizeout, int sizein1,
public TraceMemoryState binaryOp(int opcode, int sizeout, int sizein1,
TraceMemoryState in1, int sizein2, TraceMemoryState in2) {
if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) {
return TraceMemoryState.KNOWN;
@ -41,7 +60,22 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
}
@Override
public TraceMemoryState fromConst(long value, int size) {
public TraceMemoryState modBeforeStore(int sizeout, int sizeinAddress,
TraceMemoryState inAddress, int sizeinValue, TraceMemoryState inValue) {
return inValue; // Shouldn't see STORE during Sleigh eval, anyway
}
@Override
public TraceMemoryState modAfterLoad(int sizeout, int sizeinAddress, TraceMemoryState inAddress,
int sizeinValue, TraceMemoryState inValue) {
if (inAddress == TraceMemoryState.KNOWN && inValue == TraceMemoryState.KNOWN) {
return TraceMemoryState.KNOWN;
}
return TraceMemoryState.UNKNOWN;
}
@Override
public TraceMemoryState fromConst(byte[] value) {
return TraceMemoryState.KNOWN;
}
@ -51,17 +85,17 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
}
@Override
public boolean isTrue(TraceMemoryState cond) {
throw new AssertionError("Cannot decide branches using TraceMemoryState");
public TraceMemoryState fromConst(long value, int size) {
return TraceMemoryState.KNOWN;
}
@Override
public BigInteger toConcrete(TraceMemoryState value, boolean isContextreg) {
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
public byte[] toConcrete(TraceMemoryState value, Purpose purpose) {
throw new ConcretionError("Cannot make TraceMemoryState concrete", purpose);
}
@Override
public TraceMemoryState sizeOf(TraceMemoryState value) {
public long sizeOf(TraceMemoryState value) {
throw new AssertionError("Cannot get size of a TraceMemoryState");
}
}

View file

@ -20,8 +20,8 @@ import java.util.Map;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
@ -30,6 +30,18 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
/**
* The p-code execute state piece for {@link TraceMemoryState}
*
* <p>
* This state piece is meant to be used as an auxiliary to a concrete trace-bound state. See
* {@link DirectBytesTracePcodeExecutorState#withMemoryState()}. It should be used with
* {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a Sleigh
* expression's value. It essentially works like a rudimentary taint analyzer: If any part of any
* input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result is
* {@link TraceMemoryState#UNKNOWN}. This is best exemplified in {@link #getUnique(long, int)},
* though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}.
*/
public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
@ -43,7 +55,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE);
super(trace.getBaseLanguage(),
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE);
this.trace = trace;
this.snap = snap;
this.thread = thread;
@ -99,16 +113,6 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
}
}
@Override
protected long offsetToLong(byte[] offset) {
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
}
@Override
public byte[] longToOffset(AddressSpace space, long l) {
return Utils.longToBytes(l, space.getPointerSize(), language.isBigEndian());
}
@Override
protected void setUnique(long offset, int size, TraceMemoryState val) {
unique.put(range(offset, size), val);
@ -158,7 +162,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer");
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose);
}
}

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.thread.TraceThread;
/**
* Various utilities for using Sleigh with traces
*/
public enum TraceSleighUtils {
;
/**
* Get the trace memory space for the given "coordinates"
*
* <p>
* This is used to find "backing" objects for a p-code executor state bound to a trace, whether
* direct or cached.
*
* @param space the address space
* @param trace the trace
* @param thread the thread, if a register space
* @param frame the frame, if a register space
* @param toWrite true if the state intends to write to the space, i.e., the space must exist
* @return the space, or null if it doesn't exist
*/
public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace,
TraceThread thread, int frame, boolean toWrite) {
if (space.isRegisterSpace()) {
@ -47,10 +64,24 @@ public enum TraceSleighUtils {
return trace.getMemoryManager().getMemorySpace(space, toWrite);
}
/**
* Build a p-code executor that operates directly on bytes of the given trace
*
* <p>
* This execute is most suitable for evaluating Sleigh expression on a given trace snapshot, and
* for manipulating or initializing variables using Sleigh code. It is generally not suitable
* for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}.
*
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the executor
*/
public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
TraceThread thread, int frame) {
TraceBytesPcodeExecutorState state =
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Trace must use a SLEIGH language");
@ -59,10 +90,24 @@ public enum TraceSleighUtils {
BytesPcodeArithmetic.forLanguage(language), state);
}
/**
* Build a p-code executor that operates directly on bytes and memory state of the given trace
*
* <p>
* This executor is most suitable for evaluating Sleigh expressions on a given trace snapshot,
* when the client would also like to know if all variables involved are
* {@link TraceMemoryState#KNOWN}.
*
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the executor
*/
public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
Trace trace, long snap, TraceThread thread, int frame) {
TraceBytesPcodeExecutorState state =
new TraceBytesPcodeExecutorState(trace, snap, thread, frame);
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
@ -73,6 +118,16 @@ public enum TraceSleighUtils {
paired);
}
/**
* Evaluate a compiled p-code expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value of the expression as a byte array
*/
public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage();
@ -84,6 +139,16 @@ public enum TraceSleighUtils {
return expr.evaluate(executor);
}
/**
* Evaluate a compiled p-code expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value of the expression as a big integer
*/
public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) {
byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame);
@ -91,6 +156,16 @@ public enum TraceSleighUtils {
false);
}
/**
* Evaluate a compiled p-code expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value and state of the expression
*/
public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage();
@ -104,6 +179,16 @@ public enum TraceSleighUtils {
return expr.evaluate(executor);
}
/**
* Evaluate a compiled p-code expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value and state of the expression
*/
public static Pair<BigInteger, TraceMemoryState> evaluateWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) {
Pair<byte[], TraceMemoryState> bytesPair =
@ -114,6 +199,16 @@ public enum TraceSleighUtils {
bytesPair.getRight());
}
/**
* Evaluate a Sleigh expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value of the expression as a byte array
*/
public static byte[] evaluateBytes(String expr, Trace trace, long snap, TraceThread thread,
int frame) {
Language language = trace.getBaseLanguage();
@ -125,6 +220,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame);
}
/**
* Evaluate a Sleigh expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value of the expression as a big integer
*/
public static BigInteger evaluate(String expr, Trace trace, long snap, TraceThread thread,
int frame) {
Language language = trace.getBaseLanguage();
@ -135,6 +240,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame);
}
/**
* Evaluate a Sleigh expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value and state of the expression
*/
public static Entry<byte[], TraceMemoryState> evaluateBytesWithState(String expr, Trace trace,
long snap, TraceThread thread, int frame) {
Language language = trace.getBaseLanguage();
@ -146,6 +261,16 @@ public enum TraceSleighUtils {
trace, snap, thread, frame);
}
/**
* Evaluate a Sleigh expression on the given trace
*
* @param expr the expression
* @param trace the trace
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the value and state of the expression
*/
public static Entry<BigInteger, TraceMemoryState> evaluateWithState(String expr, Trace trace,
long snap, TraceThread thread, int frame) {
Language language = trace.getBaseLanguage();
@ -157,6 +282,18 @@ public enum TraceSleighUtils {
trace, snap, thread, frame);
}
/**
* Generate the expression for retrieving a memory range
*
* <p>
* In general, it does not make sense to use this directly with the above evaluation methods.
* More likely, this is used in the UI to aid the user in generating an expression. From the
* API, it's much easier to access the memory state directly.
*
* @param language the language
* @param range the range
* @return the expression
*/
public static String generateExpressionForRange(Language language, AddressRange range) {
AddressSpace space = range.getAddressSpace();
long length = range.getLength();

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.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
/**
* Various utilities used for implementing the trace database
*
* <p>
* Some of these are also useful from the API perspective. TODO: We should probably separate trace
* API utilities into another class.
*/
public enum DBTraceUtils {
;
/**
* A tuple used to index/locate a block in the trace's byte stores (memory manager)
*/
public static class OffsetSnap {
public final long offset;
public final long snap;
@ -83,6 +93,9 @@ public enum DBTraceUtils {
}
// TODO: Should this be in by default?
/**
* A codec or URLs
*/
public static class URLDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<URL, OT, StringField> {
public URLDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -125,6 +138,9 @@ public enum DBTraceUtils {
}
}
/**
* A codec for language IDs
*/
public static class LanguageIDDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<LanguageID, OT, StringField> {
@ -162,6 +178,9 @@ public enum DBTraceUtils {
}
}
/**
* A codec for compiler spec IDs
*/
public static class CompilerSpecIDDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<CompilerSpecID, OT, StringField> {
@ -199,6 +218,9 @@ public enum DBTraceUtils {
}
}
/**
* A (abstract) codec for the offset-snap tuple
*/
public abstract static class AbstractOffsetSnapDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<OffsetSnap, OT, BinaryField> {
@ -248,6 +270,7 @@ public enum DBTraceUtils {
/**
* Codec for storing {@link OffsetSnap}s as {@link BinaryField}s.
*
* <p>
* Encodes the address space ID followed by the address then the snap.
*
* @param <OT> the type of the object whose field is encoded/decoded.
@ -277,6 +300,9 @@ public enum DBTraceUtils {
}
}
/**
* A codec for reference types
*/
public static class RefTypeDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<RefType, OT, ByteField> {
public RefTypeDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -309,27 +335,103 @@ public enum DBTraceUtils {
}
}
/**
* A method outline for setting an entry in a range map where coalescing is desired
*
* @param <E> the type of entries
* @param <D> the type of range bounds
* @param <R> the type of ranges
* @param <V> the type of values
*/
public static abstract class RangeMapSetter<E, D extends Comparable<D>, R, V> {
/**
* Get the range of the given entry
*
* @param entry the entry
* @return the range
*/
protected abstract R getRange(E entry);
/**
* Get the value of the given entry
*
* @param entry the entry
* @return the value
*/
protected abstract V getValue(E entry);
/**
* Remove an entry from the map
*
* @param entry the entry
*/
protected abstract void remove(E entry);
/**
* Get the lower bound of the range
*
* @param range the range
* @return the lower bound
*/
protected abstract D getLower(R range);
/**
* Get the upper bound of the range
*
* @param range the range
* @return the upper bound
*/
protected abstract D getUpper(R range);
/**
* Create a closed range with the given bounds
*
* @param lower the lower bound
* @param upper the upper bound
* @return the range
*/
protected abstract R toRange(D lower, D upper);
/**
* Get the number immediately preceding the given bound
*
* @param d the bound
* @return the previous bound, or null if it doesn't exist
*/
protected abstract D getPrevious(D d);
/**
* Get the number immediately following the given bound
*
* @param d the bound
* @return the next bound, or null if it doesn't exist
*/
protected abstract D getNext(D d);
/**
* Get all entries intersecting the closed range formed by the given bounds
*
* @param lower the lower bound
* @param upper the upper bound
* @return the intersecting entries
*/
protected abstract Iterable<E> getIntersecting(D lower, D upper);
/**
* Place an entry into the map
*
* @param range the range of the entry
* @param value the value of the entry
* @return the new entry (or an existing entry)
*/
protected abstract E put(R range, V value);
/**
* Get the previous bound or this same bound, if the previous doesn't exist
*
* @param d the bound
* @return the previous or same bound
*/
protected D getPreviousOrSame(D d) {
D prev = getPrevious(d);
if (prev == null) {
@ -338,6 +440,12 @@ public enum DBTraceUtils {
return prev;
}
/**
* Get the next bound or this same bound, if the next doesn't exist
*
* @param d the bound
* @return the next or same bound
*/
protected D getNextOrSame(D d) {
D next = getNext(d);
if (next == null) {
@ -346,15 +454,40 @@ public enum DBTraceUtils {
return next;
}
/**
* Check if the two ranges are connected
*
* <p>
* The ranges are connected if they intersect, or if their bounds abut.
*
* @param r1 the first range
* @param r2 the second range
* @return true if connected
*/
protected boolean connects(R r1, R r2) {
return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 ||
getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 0;
}
/**
* Entry point: Set the given range to the given value, coalescing where possible
*
* @param range the range
* @param value the value
* @return the entry containing the value
*/
public E set(R range, V value) {
return set(getLower(range), getUpper(range), value);
}
/**
* Entry point: Set the given range to the given value, coalescing where possible
*
* @param lower the lower bound
* @param upper the upper bound
* @param value the value
* @return the entry containing the value
*/
public E set(D lower, D upper, V value) {
// Go one out to find abutting ranges, too.
D prev = getPreviousOrSame(lower);
@ -395,6 +528,12 @@ public enum DBTraceUtils {
}
}
/**
* A setter which works on ranges of addresses
*
* @param <E> the type of entry
* @param <V> the type of value
*/
public static abstract class AddressRangeMapSetter<E, V>
extends RangeMapSetter<E, Address, AddressRange, V> {
@Override
@ -423,6 +562,12 @@ public enum DBTraceUtils {
}
}
/**
* A setter which operates on spans of snapshot keys
*
* @param <E> the type of entry
* @param <V> the type of value
*/
public static abstract class LifespanMapSetter<E, V>
extends RangeMapSetter<E, Long, Range<Long>, V> {
@ -458,6 +603,16 @@ public enum DBTraceUtils {
}
}
/**
* Get the lower endpoint as stored in the database
*
* <p>
* {@link Long#MIN_VALUE} represents no lower bound. Endpoints should always be closed unless
* unbounded. If open, it will be converted to closed (at one greater).
*
* @param range the range
* @return the endpoint
*/
public static long lowerEndpoint(Range<Long> range) {
if (!range.hasLowerBound()) {
return Long.MIN_VALUE;
@ -468,6 +623,16 @@ public enum DBTraceUtils {
return range.lowerEndpoint().longValue() + 1;
}
/**
* Get the upper endpoint as stored in the database
*
* <p>
* {@link Long#MAX_VALUE} represents no upper bound. Endpoints should alwyas be closed unless
* unbounded. If open, it will be converted to closed (at one less).
*
* @param range the range
* @return the endpoint
*/
public static long upperEndpoint(Range<Long> range) {
if (!range.hasUpperBound()) {
return Long.MAX_VALUE;
@ -478,6 +643,13 @@ public enum DBTraceUtils {
return range.upperEndpoint().longValue() - 1;
}
/**
* Convert the given enpoints to a range
*
* @param lowerEndpoint the lower endpoint, where {@link Long#MIN_VALUE} indicates unbounded
* @param upperEndpoint the upper endpoint, where {@link Long#MAX_VALUE} indicates unbounded
* @return the range
*/
public static Range<Long> toRange(long lowerEndpoint, long upperEndpoint) {
if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) {
return Range.all();
@ -491,10 +663,27 @@ public enum DBTraceUtils {
return Range.closed(lowerEndpoint, upperEndpoint);
}
/**
* Create the range starting at the given snap, to infinity
*
* @param snap the starting snap
* @return the range [snap, +inf)
*/
public static Range<Long> toRange(long snap) {
return toRange(snap, Long.MAX_VALUE);
}
/**
* Check if the two ranges intersect
*
* <p>
* This is a bit obtuse in Guava's API, so here's the convenience method
*
* @param <T> the type of range endpoints
* @param a the first range
* @param b the second range
* @return true if they intersect
*/
public static <T extends Comparable<T>> boolean intersect(Range<T> a, Range<T> b) {
// Because we're working with a discrete domain, we have to be careful to never use open
// lower bounds. Otherwise, the following two inputs would cause a true return value when,
@ -502,10 +691,45 @@ public enum DBTraceUtils {
return a.isConnected(b) && !a.intersection(b).isEmpty();
}
/**
* Check if a given snapshot key is designated as scratch space
*
* <p>
* Conventionally, negative snaps are scratch space.
*
* @param snap the snap
* @return true if scratch space
*/
public static boolean isScratch(long snap) {
return snap < 0;
}
/**
* Form a range starting at the given snap that does not traverse both scratch and non-scratch
* space
*
* @param start the starting snap
* @return the range [start,0] if start is in scratch space, or [start, +inf) if start is not in
* scratch space
*/
public static Range<Long> atLeastMaybeScratch(long start) {
if (start < 0) {
return Range.closed(start, -1L);
}
return Range.atLeast(start);
}
/**
* "Compare" two ranges
*
* <p>
* This is just to impose a sorting order for display.
*
* @param <C> the type of endpoints
* @param a the first range
* @param b the second range
* @return the result as in {@link Comparable#compareTo(Object)}
*/
public static <C extends Comparable<C>> int compareRanges(Range<C> a, Range<C> b) {
int result;
if (!a.hasLowerBound() && b.hasLowerBound()) {
@ -548,6 +772,15 @@ public enum DBTraceUtils {
return 0;
}
/**
* Derive the table name for a given addres/register space
*
* @param baseName the base name of the table group
* @param space the address space
* @param threadKey the thread key, -1 usually indicating "no thread"
* @param frameLevel the frame level
* @return the table name
*/
public static String tableName(String baseName, AddressSpace space, long threadKey,
int frameLevel) {
if (space.isRegisterSpace()) {
@ -560,15 +793,17 @@ public enum DBTraceUtils {
}
/**
* TODO: Document me
* Truncate or delete an entry to make room
*
* <p>
* Only call this method for entries which definitely intersect the given span
* Only call this method for entries which definitely intersect the given span. This does not
* verify intersection. If the data's start snap is contained in the span to clear, the entry is
* deleted. Otherwise, it's end snap is set to one less than the span's start snap.
*
* @param data
* @param span
* @param lifespanSetter
* @param deleter
* @param data the entry subject to truncation or deletion
* @param span the span to clear up
* @param lifespanSetter the method used to truncate the entry
* @param deleter the method used to delete the entry
*/
public static <DR extends AbstractDBTraceAddressSnapRangePropertyMapData<?>> void makeWay(
DR data, Range<Long> span, BiConsumer<? super DR, Range<Long>> lifespanSetter,
@ -582,6 +817,13 @@ public enum DBTraceUtils {
lifespanSetter.accept(data, toRange(data.getY1(), lowerEndpoint(span) - 1));
}
/**
* Sutract two ranges, yielding 0, 1, or 2 ranges
*
* @param a the first range
* @param b the second range
* @return the list of ranges
*/
public static List<Range<Long>> subtract(Range<Long> a, Range<Long> b) {
RangeSet<Long> set = TreeRangeSet.create();
set.add(a);
@ -592,12 +834,25 @@ public enum DBTraceUtils {
.collect(Collectors.toList());
}
/**
* Cast an iterator to a less-specific type, given that it cannot insert elements
*
* @param <T> the desired type
* @param it the iterator of more specific type
* @return the same iterator
*/
@SuppressWarnings("unchecked")
public static <T> Iterator<T> covariantIterator(Iterator<? extends T> it) {
// Iterators only support read and remove, not insert. Safe to cast.
return (Iterator<T>) it;
}
/**
* Iterate over all the longs contained in a given range
*
* @param span the range
* @return the iterator
*/
public static Iterator<Long> iterateSpan(Range<Long> span) {
return new Iterator<>() {
final long end = upperEndpoint(span);
@ -617,6 +872,17 @@ public enum DBTraceUtils {
};
}
/**
* Get all the addresses in a factory, starting at the given place
*
* <p>
* If backward, this yields all addresses coming before start
*
* @param factory the factory
* @param start the start (or end) address
* @param forward true for all after, false for all before
* @return the address set
*/
public static AddressSetView getAddressSet(AddressFactory factory, Address start,
boolean forward) {
AddressSet all = factory.getAddressSet();
@ -628,6 +894,14 @@ public enum DBTraceUtils {
return factory.getAddressSet(min, start);
}
/**
* Create an address range, checking the endpoints
*
* @param min the min address, which must be less than or equal to max
* @param max the max address, which must be greater than or equal to min
* @return the range
* @throws IllegalArgumentException if max is less than min
*/
public static AddressRange toRange(Address min, Address max) {
if (min.compareTo(max) > 0) {
throw new IllegalArgumentException("min must precede max");

View file

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

View file

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

View file

@ -18,26 +18,29 @@ package ghidra.trace.database.map;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import com.google.common.collect.Range;
import db.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.property.TracePropertyMap;
import ghidra.trace.model.property.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.*;
import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.annot.*;
import ghidra.util.exception.NotYetImplementedException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@ -53,10 +56,12 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
dataFactory);
}
// TODO: These next several methods are repeated thrice in this file....
@SuppressWarnings("unchecked")
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
// TODO: Would rather not rely on implementation knowledge here
// The shape is the database record in AbstracctDBTraceAddressSnapRangePropertyMapData
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
makeWay((DR) entry.getKey(), span);
}
@ -87,12 +92,21 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
}
@Override
public void clear(Range<Long> span, AddressRange range) {
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
AddressRange range) {
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
}
@Override
public boolean clear(Range<Long> span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span);
result = true;
}
return result;
}
}
@ -107,6 +121,211 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
}
}
@Override
protected DBTracePropertyMapSpace createSpace(AddressSpace space,
DBTraceSpaceEntry ent) throws VersionException, IOException {
return new DBTracePropertyMapSpace(
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
lock, space, dataType, dataFactory);
}
@Override
protected DBTracePropertyMapRegisterSpace createRegisterSpace(
AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent)
throws VersionException, IOException {
return new DBTracePropertyMapRegisterSpace(
tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(),
lock, space, thread, ent.getFrameLevel(), dataType, dataFactory);
}
@Override
public TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space,
boolean createIfAbsent) {
return (DBTracePropertyMapSpace) getForSpace(space, createIfAbsent);
}
@Override
public TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread,
int frameLevel, boolean createIfAbsent) {
return (DBTracePropertyMapRegisterSpace) getForRegisterSpace(thread, frameLevel,
createIfAbsent);
}
@Override
public void delete() {
throw new NotYetImplementedException();
}
public class DBTracePropertyMapSpace
extends DBTraceAddressSnapRangePropertyMapSpace<T, DR>
implements TracePropertyMapSpace<T> {
public DBTracePropertyMapSpace(String tableName, DBCachedObjectStoreFactory storeFactory,
ReadWriteLock lock, AddressSpace space, Class<DR> dataType,
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
throws VersionException, IOException {
super(tableName, storeFactory, lock, space, dataType, dataFactory);
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public Class<T> getValueClass() {
return AbstractDBTracePropertyMap.this.getValueClass();
}
@SuppressWarnings("unchecked")
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
// TODO: Would rather not rely on implementation knowledge here
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
makeWay((DR) entry.getKey(), span);
}
protected void makeWay(DR data, Range<Long> span) {
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
// TODO: Any events?
}
@Override
public void set(Range<Long> lifespan, Address address, T value) {
put(address, lifespan, value);
}
@Override
public void set(Range<Long> lifespan, AddressRange range, T value) {
put(range, lifespan, value);
}
@Override
public T get(long snap, Address address) {
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
}
@Override
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
}
@Override
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
AddressRange range) {
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
}
@Override
public boolean clear(Range<Long> span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span);
result = true;
}
return result;
}
}
@Override
public T put(TraceAddressSnapRange shape, T value) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
makeWay(entry, shape.getLifespan());
}
return super.put(shape, value);
}
}
}
public class DBTracePropertyMapRegisterSpace
extends DBTraceAddressSnapRangePropertyMapRegisterSpace<T, DR>
implements TracePropertyMapRegisterSpace<T> {
public DBTracePropertyMapRegisterSpace(String tableName,
DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space,
TraceThread thread, int frameLevel, Class<DR> dataType,
DBTraceAddressSnapRangePropertyMapDataFactory<T, DR> dataFactory)
throws VersionException, IOException {
super(tableName, storeFactory, lock, space, thread, frameLevel, dataType, dataFactory);
// TODO Auto-generated constructor stub
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public Class<T> getValueClass() {
return AbstractDBTracePropertyMap.this.getValueClass();
}
@SuppressWarnings("unchecked")
protected void makeWay(Entry<TraceAddressSnapRange, T> entry, Range<Long> span) {
// TODO: Would rather not rely on implementation knowledge here
// The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData
makeWay((DR) entry.getKey(), span);
}
protected void makeWay(DR data, Range<Long> span) {
DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d));
// TODO: Any events?
}
@Override
public void set(Range<Long> lifespan, Address address, T value) {
put(address, lifespan, value);
}
@Override
public void set(Range<Long> lifespan, AddressRange range, T value) {
put(range, lifespan, value);
}
@Override
public T get(long snap, Address address) {
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
}
@Override
public Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address) {
return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry();
}
@Override
public Collection<Entry<TraceAddressSnapRange, T>> getEntries(Range<Long> lifespan,
AddressRange range) {
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
}
@Override
public boolean clear(Range<Long> span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span);
result = true;
}
return result;
}
}
@Override
public T put(TraceAddressSnapRange shape, T value) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
makeWay(entry, shape.getLifespan());
}
return super.put(shape, value);
}
}
}
public static class DBTraceIntPropertyMap
extends AbstractDBTracePropertyMap<Integer, DBTraceIntPropertyMapEntry> {

View file

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

View file

@ -17,112 +17,435 @@ package ghidra.trace.database.program;
import java.util.Iterator;
import ghidra.program.model.address.Address;
import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.program.model.util.*;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.property.TracePropertyMap;
import ghidra.util.LockHold;
import ghidra.util.Saveable;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.*;
import ghidra.util.prop.PropertyVisitor;
import ghidra.util.task.TaskMonitor;
public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager {
protected final DBTraceProgramView program;
protected abstract class AbstractDBTraceProgramViewPropertyMap<T> implements PropertyMap {
protected final TracePropertyMap<T> map;
protected final String name;
public AbstractDBTraceProgramViewPropertyMap(TracePropertyMap<T> map, String name) {
this.map = map;
this.name = name;
}
@Override
public String getName() {
return name;
}
protected AddressSetView getAddressSetView() {
return map.getAddressSetView(Range.singleton(program.snap));
}
@Override
public boolean intersects(Address start, Address end) {
return getAddressSetView().intersects(start, end);
}
@Override
public boolean intersects(AddressSetView set) {
return getAddressSetView().intersects(set);
}
@Override
public boolean removeRange(Address start, Address end) {
return map.clear(Range.singleton(program.snap), new AddressRangeImpl(start, end));
}
@Override
public boolean remove(Address addr) {
return removeRange(addr, addr);
}
@Override
public boolean hasProperty(Address addr) {
return intersects(addr, addr);
}
@Override
public T getObject(Address addr) {
return map.get(program.snap, addr);
}
@Override
public Address getNextPropertyAddress(Address addr) {
Address next = addr.next();
if (next == null) {
return null;
}
AddressRangeIterator it = getAddressSetView().getAddressRanges(next, true);
if (!it.hasNext()) {
return null;
}
AddressRange range = it.next();
if (!range.contains(next)) {
return next;
}
return range.getMinAddress();
}
@Override
public Address getPreviousPropertyAddress(Address addr) {
Address prev = addr.previous();
if (prev == null) {
return null;
}
AddressRangeIterator it = getAddressSetView().getAddressRanges(prev, false);
if (!it.hasNext()) {
return null;
}
AddressRange range = it.next();
if (!range.contains(prev)) {
return prev;
}
return range.getMaxAddress();
}
@Override
public Address getFirstPropertyAddress() {
return getAddressSetView().getMinAddress();
}
@Override
public Address getLastPropertyAddress() {
return getAddressSetView().getMaxAddress();
}
@Override
public int getSize() {
return (int) getAddressSetView().getNumAddresses();
}
@Override
public AddressIterator getPropertyIterator(Address start, Address end) {
return getPropertyIterator(start, end, true);
}
@Override
public AddressIterator getPropertyIterator(Address start, Address end, boolean forward) {
return getAddressSetView().intersectRange(start, end).getAddresses(forward);
}
@Override
public AddressIterator getPropertyIterator() {
return getAddressSetView().getAddresses(true);
}
@Override
public AddressIterator getPropertyIterator(AddressSetView asv) {
return getPropertyIterator(asv, true);
}
@Override
public AddressIterator getPropertyIterator(AddressSetView asv, boolean forward) {
return getAddressSetView().intersect(asv).getAddresses(forward);
}
@Override
public AddressIterator getPropertyIterator(Address start, boolean forward) {
return getAddressSetView().getAddresses(start, forward);
}
@Override
public void moveRange(Address start, Address end, Address newStart) {
throw new UnsupportedOperationException();
}
}
protected class DBTraceProgramViewIntPropertyMap
extends AbstractDBTraceProgramViewPropertyMap<Integer> implements IntPropertyMap {
public DBTraceProgramViewIntPropertyMap(TracePropertyMap<Integer> map, String name) {
super(map, name);
}
@Override
public void applyValue(PropertyVisitor visitor, Address addr) {
Integer value = getObject(addr);
if (value == null) {
return;
}
visitor.visit(value.intValue());
}
@Override
public void add(Address addr, int value) {
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
}
@Override
public int getInt(Address addr) throws NoValueException {
Integer value = getObject(addr);
if (value == null) {
throw new NoValueException();
}
return value;
}
}
protected class DBTraceProgramViewLongPropertyMap
extends AbstractDBTraceProgramViewPropertyMap<Long> implements LongPropertyMap {
public DBTraceProgramViewLongPropertyMap(TracePropertyMap<Long> map, String name) {
super(map, name);
}
@Override
public void applyValue(PropertyVisitor visitor, Address addr) {
Long value = getObject(addr);
if (value == null) {
return;
}
// TODO: In program, this throws NotYetImplemented....
visitor.visit(value.longValue());
}
@Override
public void add(Address addr, long value) {
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
}
@Override
public long getLong(Address addr) throws NoValueException {
Long value = getObject(addr);
if (value == null) {
throw new NoValueException();
}
return value;
}
}
protected class DBTraceProgramViewStringPropertyMap
extends AbstractDBTraceProgramViewPropertyMap<String> implements StringPropertyMap {
public DBTraceProgramViewStringPropertyMap(TracePropertyMap<String> map, String name) {
super(map, name);
}
@Override
public void applyValue(PropertyVisitor visitor, Address addr) {
String value = getObject(addr);
visitor.visit(value);
}
@Override
public void add(Address addr, String value) {
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value);
}
@Override
public String getString(Address addr) {
return getObject(addr);
}
}
protected class DBTraceProgramViewObjectPropertyMap<T extends Saveable>
extends AbstractDBTraceProgramViewPropertyMap<T> implements ObjectPropertyMap {
public DBTraceProgramViewObjectPropertyMap(TracePropertyMap<T> map, String name) {
super(map, name);
}
@Override
public void applyValue(PropertyVisitor visitor, Address addr) {
Saveable value = getObject(addr);
visitor.visit(value);
}
@Override
public void add(Address addr, Saveable value) {
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr,
map.getValueClass().cast(value));
}
@Override
public Class<?> getObjectClass() {
return map.getValueClass();
}
}
protected class DBTraceProgramViewVoidPropertyMap
extends AbstractDBTraceProgramViewPropertyMap<Void> implements VoidPropertyMap {
public DBTraceProgramViewVoidPropertyMap(TracePropertyMap<Void> map, String name) {
super(map, name);
}
@Override
public void applyValue(PropertyVisitor visitor, Address addr) {
if (!hasProperty(addr)) {
return;
}
visitor.visit();
}
@Override
public void add(Address addr) {
map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, null);
}
}
public DBTraceProgramViewPropertyMapManager(DBTraceProgramView program) {
this.program = program;
}
@Override
public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException {
// TODO Auto-generated method stub
return null;
return new DBTraceProgramViewIntPropertyMap(program.trace.getAddressPropertyManager()
.createPropertyMap(propertyName, Integer.class),
propertyName);
}
@Override
public LongPropertyMap createLongPropertyMap(String propertyName)
throws DuplicateNameException {
// TODO Auto-generated method stub
return null;
return new DBTraceProgramViewLongPropertyMap(program.trace.getAddressPropertyManager()
.createPropertyMap(propertyName, Long.class),
propertyName);
}
@Override
public StringPropertyMap createStringPropertyMap(String propertyName)
throws DuplicateNameException {
// TODO Auto-generated method stub
return null;
return new DBTraceProgramViewStringPropertyMap(program.trace.getAddressPropertyManager()
.createPropertyMap(propertyName, String.class),
propertyName);
}
@Override
public ObjectPropertyMap createObjectPropertyMap(String propertyName,
Class<? extends Saveable> objectClass) throws DuplicateNameException {
// TODO Auto-generated method stub
return null;
return new DBTraceProgramViewObjectPropertyMap<>(program.trace.getAddressPropertyManager()
.createPropertyMap(propertyName, objectClass),
propertyName);
}
@Override
public VoidPropertyMap createVoidPropertyMap(String propertyName)
throws DuplicateNameException {
// TODO Auto-generated method stub
return null;
return new DBTraceProgramViewVoidPropertyMap(program.trace.getAddressPropertyManager()
.createPropertyMap(propertyName, Void.class),
propertyName);
}
@Override
@SuppressWarnings("unchecked")
public PropertyMap getPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<?> map =
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
if (map == null) {
return null;
}
Class<?> cls = map.getValueClass();
if (cls == Integer.class) {
return new DBTraceProgramViewIntPropertyMap((TracePropertyMap<Integer>) map,
propertyName);
}
if (cls == Long.class) {
return new DBTraceProgramViewLongPropertyMap((TracePropertyMap<Long>) map,
propertyName);
}
if (cls == String.class) {
return new DBTraceProgramViewStringPropertyMap((TracePropertyMap<String>) map,
propertyName);
}
if (cls == Void.class) {
return new DBTraceProgramViewVoidPropertyMap((TracePropertyMap<Void>) map,
propertyName);
}
if (Saveable.class.isAssignableFrom(cls)) {
return new DBTraceProgramViewObjectPropertyMap<>(
(TracePropertyMap<? extends Saveable>) map, propertyName);
}
throw new AssertionError("Where did this property map type come from? " + cls);
}
@Override
public IntPropertyMap getIntPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<Integer> map = program.trace.getAddressPropertyManager()
.getPropertyMap(propertyName, Integer.class);
return map == null ? null : new DBTraceProgramViewIntPropertyMap(map, propertyName);
}
@Override
public LongPropertyMap getLongPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<Long> map = program.trace.getAddressPropertyManager()
.getPropertyMap(propertyName, Long.class);
return map == null ? null : new DBTraceProgramViewLongPropertyMap(map, propertyName);
}
@Override
public StringPropertyMap getStringPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<String> map = program.trace.getAddressPropertyManager()
.getPropertyMap(propertyName, String.class);
return map == null ? null : new DBTraceProgramViewStringPropertyMap(map, propertyName);
}
@Override
@SuppressWarnings("unchecked")
public ObjectPropertyMap getObjectPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<?> map =
program.trace.getAddressPropertyManager().getPropertyMap(propertyName);
if (map == null) {
return null;
}
if (!Saveable.class.isAssignableFrom(map.getValueClass())) {
throw new TypeMismatchException("Property " + propertyName + " is not object type");
}
return new DBTraceProgramViewObjectPropertyMap<>((TracePropertyMap<? extends Saveable>) map,
propertyName);
}
@Override
public VoidPropertyMap getVoidPropertyMap(String propertyName) {
// TODO Auto-generated method stub
return null;
TracePropertyMap<Void> map = program.trace.getAddressPropertyManager()
.getPropertyMap(propertyName, Void.class);
return map == null ? null : new DBTraceProgramViewVoidPropertyMap(map, propertyName);
}
@Override
public boolean removePropertyMap(String propertyName) {
// TODO Auto-generated method stub
return false;
// It would delete for entire trace, not just this view
throw new UnsupportedOperationException();
}
@Override
public Iterator<String> propertyManagers() {
// TODO Auto-generated method stub
return null;
return program.trace.getAddressPropertyManager().getAllProperties().keySet().iterator();
}
protected void removeAll(Range<Long> span, AddressRange range) {
try (LockHold hold = program.trace.lockWrite()) {
for (TracePropertyMap<?> map : program.trace.getAddressPropertyManager()
.getAllProperties()
.values()) {
map.clear(span, range);
}
}
}
@Override
public void removeAll(Address addr) {
// TODO Auto-generated method stub
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), new AddressRangeImpl(addr, addr));
}
@Override
public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor)
throws CancelledException {
// TODO Auto-generated method stub
removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap),
new AddressRangeImpl(startAddr, endAddr));
}
}

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

View file

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

View file

@ -21,6 +21,13 @@ import ghidra.program.model.util.TypeMismatchException;
import ghidra.util.Saveable;
import ghidra.util.exception.DuplicateNameException;
/**
* The manager for user properties of a trace
*
* <p>
* Clients may create property maps of various value types. Each map is named, also considered the
* "property name," and can be retrieve by that name.
*/
public interface TraceAddressPropertyManager {
/**
* Create a property map with the given name having the given type
@ -59,6 +66,16 @@ public interface TraceAddressPropertyManager {
*/
<T> TracePropertyMap<T> getPropertyMap(String name, Class<T> valueClass);
/**
* Get the property map with the given name, if its values extend the given type
*
* @param name the name
* @param valueClass the expected type of values
* @return the property map, or null if it does not exist
* @throws TypeMismatchException if it exists but does not have the expected type
*/
<T> TracePropertyMap<? extends T> getPropertyMapExtends(String name, Class<T> valueClass);
/**
* Get the property map with the given name, creating it if necessary, of the given type
*
@ -70,23 +87,17 @@ public interface TraceAddressPropertyManager {
<T> TracePropertyMap<T> getOrCreatePropertyMap(String name, Class<T> valueClass);
/**
* Get the property map with the given name, if its type extends the given type
* Get the property map with the given name, creating it if necessary, of the given type
*
* <p>
* If the map already exists, then its values' type must be a super type of that given.
*
* @see #getOrCreatePropertyMap(String, Class)
* @param name the name
* @param valueClass the expected type of values to get
* @return the property map suitable for getting values of the given type
* @param valueClass the expected type of values
* @return the (possibly new) property map
*/
<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)
* @param name the name
* @param valueClass the expected type of values to set
* @return the property map suitable for setting values of the given type
*/
<T> TracePropertySetter<T> getOrCreatePropertySetter(String name, Class<T> valueClass);
<T> TracePropertyMap<? super T> getOrCreatePropertyMapSuper(String name, Class<T> valueClass);
/**
* Get the property map with the given name.
@ -94,8 +105,8 @@ public interface TraceAddressPropertyManager {
* <p>
* Note that no type checking is performed (there is no {@code valueClass} parameter). Thus, the
* returned map is suitable only for clearing and querying where the property is present. The
* caller may perform run-time type checking via the {@link TracePropertyMap#getValueClass()}
* method.
* caller may perform run-time type checking via the
* {@link TracePropertyMapOperations#getValueClass()} method.
*
* @param name the name
* @return the property map

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;
import java.util.Map;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
/**
* A map from address-snap pairs to user-defined values in a {@link Trace}
* A range map for storing properties in a trace
*
* <p>
* Technically, each range is actually a "box" in two dimensions: time and space. Time is
* represented by the span of snapshots covered, and space is represented by the range of addresses
* covered. Currently, no effort is made to optimize coverage for entries having the same value. For
* operations on entries, see {@link TracePropertyMapOperations}.
*
* <p>
* This interface is the root of a multi-space property map. For memory spaces, clients can
* generally use the operations inherited on this interface. For register spaces, clients must use
* {@link #getPropertyMapRegisterSpace(TraceThread, int, boolean)} or similar.
*
* @param <T> the type of values
*/
public interface TracePropertyMap<T> extends TracePropertySetter<T>, TracePropertyGetter<T> {
public interface TracePropertyMap<T> extends TracePropertyMapOperations<T> {
/**
* Get the class for values of the map
* Get the map space for the given address space
*
* @return the value class
* @param space the address space
* @param createIfAbsent true to create the map space if it doesn't already exist
* @return the space, or null
*/
@Override
Class<T> getValueClass();
TracePropertyMapSpace<T> getPropertyMapSpace(AddressSpace space, boolean createIfAbsent);
/**
* Get the entry at the given address-snap pair
* Get the map space for the registers of a given thread and frame
*
* @param thread the thread
* @param frameLevel the frame level, 0 being the innermost
* @param createIfAbsent true to create the map space if it doesn't already exist
* @return the space, or null
*/
TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceThread thread, int frameLevel,
boolean createIfAbsent);
/**
* Get the map space for the registers of a given frame (which knows its thread)
*
* @param frame the frame
* @param createIfAbsent true to create the map space if it doesn't already exist
* @return the space, or null
*/
default TracePropertyMapRegisterSpace<T> getPropertyMapRegisterSpace(TraceStackFrame frame,
boolean createIfAbsent) {
return getPropertyMapRegisterSpace(frame.getStack().getThread(), frame.getLevel(),
createIfAbsent);
}
/**
* Get the map space for the given trace space
*
* @param traceSpace the trace space, giving the memory space or thread/frame register space
* @param createIfAbsent true to create the map space if it doesn't already exist
* @return the space, or null
*/
default TracePropertyMapSpace<T> getPropertyMapSpace(TraceAddressSpace traceSpace,
boolean createIfAbsent) {
if (traceSpace.getAddressSpace().isRegisterSpace()) {
return getPropertyMapRegisterSpace(traceSpace.getThread(), traceSpace.getFrameLevel(),
createIfAbsent);
}
return getPropertyMapSpace(traceSpace.getAddressSpace(), createIfAbsent);
}
/**
* Delete this property and remove all of its maps
*
* <p>
* Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in
* {@link TracePropertyGetter}.
*
* @param snap the snap
* @param address the address
* @return the entry, which includes the ranges and the value
* The property can be re-created with the same or different value type.
*/
Map.Entry<TraceAddressSnapRange, T> getEntry(long snap, Address address);
void delete();
}

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