Merge remote-tracking branch 'origin/GP-1230_Dan_taint-REBASED-SQUASHED'

This commit is contained in:
Ryan Kurtz
2022-08-22 14:19:17 -04:00
205 changed files with 11214 additions and 3714 deletions
+1 -1
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')
@@ -2,7 +2,9 @@ AutoReadMemorySpec
DebuggerBot
DebuggerMappingOpinion
DebuggerModelFactory
DebuggerPcodeEmulatorFactory
DebuggerPlatformOpinion
DebuggerProgramLaunchOpinion
DebuggerRegisterColumnFactory
DisassemblyInject
LocationTrackingSpec
@@ -28,7 +28,7 @@ import java.util.List;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
@@ -136,7 +136,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
* library. This emulator will still know how to integrate with the UI, reading through to
* open programs and writing state back into the trace.
*/
DebuggerTracePcodeEmulator emulator = new DebuggerTracePcodeEmulator(tool, trace, 0, null) {
BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(tool, trace, 0, null) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
@@ -169,7 +169,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
thread.stepInstruction();
snapshot =
time.createSnapshot("Stepped to " + thread.getCounter());
emulator.writeDown(trace, snapshot.getKey(), 0, false);
emulator.writeDown(trace, snapshot.getKey(), 0);
}
printerr("We should not have completed 10 steps!");
}
@@ -29,16 +29,17 @@ import ghidra.program.model.pcode.Varnode;
* A userop library for the emulator
*
* <p>
* If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These
* libraries allow you to implement userop, including those declared by the language. Without these,
* the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also
* define new userops, which can be invoked from Sleigh code injected into the emulator.
* If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries
* allow you to implement userops, including those declared by the language. Without these, the
* emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define
* new userops, which can be invoked from Sleigh code injected into the emulator.
*
* <p>
* These libraries can have both Java-callback and p-code implementations of userops. If only using
* p-code implementations, the library can be parameterized with type {@code <T>} and just pass that
* over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes
* concrete bytes, we will fix the library's type to {@code byte[]}.
* concrete bytes, we will fix the library's type to {@code byte[]}. With careful use of the
* {@link PcodeArithmetic}, you can keep the type an abstract {@code <T>} with Java callbacks.
*
* <p>
* Methods in this class (not including those in its nested classes) are implemented as Java
@@ -74,8 +75,7 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]>
* @return the length of the string in bytes
*/
@PcodeUserop
public byte[] print_utf8(@OpState PcodeExecutorStatePiece<byte[], byte[]> state,
byte[] start) {
public byte[] print_utf8(@OpState PcodeExecutorState<byte[]> state, byte[] start) {
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
long end = offset;
while (state.getVar(space, end, 1, true)[0] != 0) {
@@ -23,6 +23,7 @@ import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace;
@@ -41,7 +42,8 @@ import ghidra.program.model.listing.Program;
* call libraries typically implement that interface by annotating p-code userops with
* {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured
* Sleigh. Conventionally, the Java method names of system calls should be
* <em>platform</em>_<em>name</em>. This is to prevent name-space pollution of userops.
* <em>platform</em>_<em>name</em>. This is to prevent name conflicts among userops when several
* libraries are composed.
*
* <p>
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
@@ -53,7 +55,7 @@ import ghidra.program.model.listing.Program;
*
* <p>
* For demonstration, this will implement one from scratch for no particular operating system, but
* it will borrow many conventions from linux-amd64.
* it will borrow many conventions from Linux-amd64.
*/
public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
private final static Charset UTF8 = Charset.forName("utf8");
@@ -80,11 +82,11 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
/**
* Because the system call numbering is derived from the "syscall" overlay on OTHER space, a
* program is required. The system call analyzer must be applied to it. The program and its
* compiler spec are also used to derive (what it can of) the system call ABI. Notably, it
* applies the calling convention of the functions placed in syscall overlay. Those parts which
* cannot (yet) be derived from the program are instead implemented as abstract methods of this
* class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* program is required. Use the system call analyzer on your program to populate this space. The
* program and its compiler spec are also used to derive (what it can of) the system call ABI.
* Notably, it applies the calling convention of the functions placed in syscall overlay. Those
* parts which cannot (yet) be derived from the program are instead implemented as abstract
* methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
*
* @param machine the emulator
@@ -151,7 +153,7 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
* <p>
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the
* userop name should be prefixed with the platform name, to avoid naming collisions among
* userops.
* composed libraries.
*
* <p>
* For demonstration, we will export this as a system call, though that is not required for
@@ -173,8 +175,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
* copy of the arithmetic as a field at library construction time.
*/
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
long strLong = arithmetic.toConcrete(str).longValue();
long endLong = arithmetic.toConcrete(end).longValue();
long strLong = arithmetic.toLong(str, Purpose.LOAD);
long endLong = arithmetic.toLong(end, Purpose.OTHER);
byte[] stringBytes =
machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true);
@@ -185,12 +187,17 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
// Second, a Structured Sleigh example
/**
* The nested class for syscall implemented using StructuredSleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare it
* public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()}.
* The nested class for syscalls implemented using Structured Sleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
* class public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()} to provide the processor private access.
*/
public class DemoStructuredPart extends StructuredPart {
/**
* This creates a handle to the "demo_write" p-code userop for use in Structured Sleigh.
* Otherwise, there's no way to refer to the userop. Think of it like a "forward" or
* "external" declaration.
*/
UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
/**
@@ -21,8 +21,6 @@
//@menupath
//@toolbar
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
@@ -88,7 +86,7 @@ public class StandAloneEmuExampleScript extends GhidraScript {
*/
Address entry = dyn.getAddress(0x00400000);
Assembler asm = Assemblers.getAssembler(language);
CodeBuffer buffer = new CodeBuffer(asm, entry);
AssemblyBuffer buffer = new AssemblyBuffer(asm, entry);
buffer.assemble("MOV RCX, 0xdeadbeef");
Address injectHere = buffer.getNext();
buffer.assemble("MOV RAX, 1");
@@ -150,30 +148,4 @@ public class StandAloneEmuExampleScript extends GhidraScript {
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
}
public static class CodeBuffer {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final Assembler asm;
private final Address entry;
public CodeBuffer(Assembler asm, Address entry) {
this.asm = asm;
this.entry = entry;
}
public Address getNext() {
return entry.add(baos.size());
}
public byte[] assemble(String line)
throws AssemblySyntaxException, AssemblySemanticException, IOException {
byte[] bytes = asm.assembleLine(getNext(), line);
baos.write(bytes);
return bytes;
}
public byte[] getBytes() {
return baos.toByteArray();
}
}
}
@@ -40,5 +40,11 @@
current address and whose registers are initialized to the register context at the current
address. Optionally, other registers can be initialized via the UI or a script. The new thread
is activated so that stepping actions will affect it by default.</P>
<H3><A name="configure_emulator"></A> Configure Emulator</H3>
<P>This action is always available. It lists emulators available for configuration. Selecting
one will set it as the current emulator. The next time emulation is activated, it will use the
selected emulator.</P>
</BODY>
</HTML>
@@ -28,18 +28,19 @@ import ghidra.util.table.GhidraTableFilterPanel;
public abstract class AbstractDebuggerMapProposalDialog<R> extends DialogComponentProvider {
protected final EnumeratedColumnTableModel<R> tableModel = createTableModel();
protected final EnumeratedColumnTableModel<R> tableModel;
protected GTable table;
protected GhidraTableFilterPanel<R> filterPanel;
private Collection<R> adjusted;
protected AbstractDebuggerMapProposalDialog(String title) {
protected AbstractDebuggerMapProposalDialog(PluginTool tool, String title) {
super(title, true, true, true, false);
tableModel = createTableModel(tool);
populateComponents();
}
protected abstract EnumeratedColumnTableModel<R> createTableModel();
protected abstract EnumeratedColumnTableModel<R> createTableModel(PluginTool tool);
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
@@ -143,16 +143,17 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider {
}
}
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel =
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel;
GTable table;
GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
private Entry<Program, MemoryBlock> chosen;
public DebuggerBlockChooserDialog() {
public DebuggerBlockChooserDialog(PluginTool tool) {
super("Memory Blocks", true, true, true, false);
tableModel =
new DefaultEnumeratedColumnTableModel<>(tool, "Blocks", MemoryBlockTableColumns.class);
populateComponents();
}
@@ -572,6 +572,21 @@ public interface DebuggerResources {
}
}
interface ConfigureEmulatorAction {
String NAME = "Configure Emulator";
String DESCRIPTION = "Choose and configure the current emulator";
String GROUP = GROUP_MAINTENANCE;
String HELP_ANCHOR = "configure_emulator";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
abstract class AbstractQuickLaunchAction extends DockingAction {
public static final String NAME = "Quick Launch";
public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon?
@@ -93,7 +93,7 @@ public abstract class DebuggerGoToTrait {
}
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) {
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {
Address address = space.getAddress(
@@ -134,7 +134,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> {
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
super(provider.getTool(), "Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
lb -> new LogicalBreakpointRow(provider, lb));
}
@@ -212,8 +212,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) {
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey,
loc -> new BreakpointLocationRow(provider, loc));
super(provider.getTool(), "Locations", BreakpointLocationTableColumns.class,
TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc));
}
@Override
@@ -45,8 +45,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.util.*;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@@ -205,8 +204,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
LogTableColumns, ActionContext, LogRow, LogRow> {
public LogTableModel() {
super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
public LogTableModel(PluginTool tool) {
super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
}
@Override
@@ -286,7 +285,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
protected final Map<String, Map<String, DockingActionIf>> actionsByOwnerThenName =
new LinkedHashMap<>();
protected final LogTableModel logTableModel = new LogTableModel();
protected final LogTableModel logTableModel;
protected GhidraTable logTable;
private GhidraTableFilterPanel<LogRow> logFilterPanel;
@@ -301,6 +300,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
this.plugin = plugin;
logTableModel = new LogTableModel(tool);
tool.addPopupActionProvider(this);
setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE);
@@ -55,7 +55,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
return ctx.hasSelection() ? ctx.getSelection() : null;
}
protected DebuggerCopyIntoProgramDialog copyDialog = new DebuggerCopyIntoProgramDialog();
protected DebuggerCopyIntoProgramDialog copyDialog;
protected DockingAction actionExportView;
protected DockingAction actionCopyIntoCurrentProgram;
@@ -70,6 +70,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
public DebuggerCopyActionsPlugin(PluginTool tool) {
super(tool);
copyDialog = new DebuggerCopyIntoProgramDialog(tool);
createActions();
}
@@ -37,6 +37,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
@@ -200,8 +201,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
protected static class RangeTableModel
extends DefaultEnumeratedColumnTableModel<RangeTableColumns, RangeEntry> {
public RangeTableModel() {
super("Ranges", RangeTableColumns.class);
public RangeTableModel(PluginTool tool) {
super(tool, "Ranges", RangeTableColumns.class);
}
@Override
@@ -302,15 +303,16 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
protected JCheckBox cbUseOverlays;
protected DebuggerCopyPlan plan = new DebuggerCopyPlan();
protected final RangeTableModel tableModel = new RangeTableModel();
protected final RangeTableModel tableModel;
protected GTable table;
protected GhidraTableFilterPanel<RangeEntry> filterPanel;
protected JButton resetButton;
public DebuggerCopyIntoProgramDialog() {
public DebuggerCopyIntoProgramDialog(PluginTool tool) {
super("Copy Into Program", true, true, true, true);
tableModel = new RangeTableModel(tool);
populateComponents();
}
@@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
@@ -101,8 +102,8 @@ public class DebuggerRegionMapProposalDialog
protected static class RegionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<RegionMapTableColumns, RegionMapEntry> {
public RegionMapPropsalTableModel() {
super("Region Map", RegionMapTableColumns.class);
public RegionMapPropsalTableModel(PluginTool tool) {
super(tool, "Region Map", RegionMapTableColumns.class);
}
@Override
@@ -114,13 +115,13 @@ public class DebuggerRegionMapProposalDialog
private final DebuggerRegionsProvider provider;
public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) {
super(MapRegionsAction.NAME);
super(provider.getTool(), MapRegionsAction.NAME);
this.provider = provider;
}
@Override
protected RegionMapPropsalTableModel createTableModel() {
return new RegionMapPropsalTableModel();
protected RegionMapPropsalTableModel createTableModel(PluginTool tool) {
return new RegionMapPropsalTableModel(tool);
}
@Override
@@ -44,8 +44,7 @@ import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTab
import ghidra.app.services.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
@@ -124,8 +123,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
public RegionTableModel() {
super("Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
public RegionTableModel(PluginTool tool) {
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new);
}
}
@@ -233,7 +232,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
private final RegionsListener regionsListener = new RegionsListener();
protected final RegionTableModel regionTableModel = new RegionTableModel();
protected final RegionTableModel regionTableModel;
protected GhidraTable regionTable;
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
@@ -260,6 +259,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
DebuggerRegionActionContext.class);
this.plugin = plugin;
regionTableModel = new RegionTableModel(tool);
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
setWindowMenuGroup(DebuggerPluginPackage.NAME);
@@ -268,7 +269,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
blockChooserDialog = new DebuggerBlockChooserDialog();
blockChooserDialog = new DebuggerBlockChooserDialog(tool);
regionProposalDialog = new DebuggerRegionMapProposalDialog(this);
setDefaultWindowPosition(WindowPosition.BOTTOM);
@@ -1,243 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.modules;
import java.awt.BorderLayout;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.modules.TraceSection;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
static class MemoryBlockRow {
private final Program program;
private final MemoryBlock block;
private double score;
public MemoryBlockRow(Program program, MemoryBlock block) {
this.program = program;
this.block = block;
}
public Program getProgram() {
return program;
}
public MemoryBlock getBlock() {
return block;
}
public String getProgramName() {
DomainFile df = program.getDomainFile();
if (df != null) {
return df.getName();
}
return program.getName();
}
public String getBlockName() {
return block.getName();
}
public Address getMinAddress() {
return block.getStart();
}
public Address getMaxAddress() {
return block.getEnd();
}
public long getLength() {
return block.getSize();
}
public double getScore() {
return score;
}
public double score(TraceSection section, DebuggerStaticMappingService service) {
if (section == null) {
return score = 0;
}
return score = service.proposeSectionMap(section, program, block).computeScore();
}
public ProgramLocation getProgramLocation() {
return new ProgramLocation(program, block.getStart());
}
}
enum MemoryBlockTableColumns
implements EnumeratedTableColumn<MemoryBlockTableColumns, MemoryBlockRow> {
SCORE("Score", Double.class, MemoryBlockRow::getScore, SortDirection.DESCENDING),
PROGRAM("Program", String.class, MemoryBlockRow::getProgramName, SortDirection.ASCENDING),
BLOCK("Block", String.class, MemoryBlockRow::getBlockName, SortDirection.ASCENDING),
START("Start Address", Address.class, MemoryBlockRow::getMinAddress, SortDirection.ASCENDING),
END("End Address", Address.class, MemoryBlockRow::getMaxAddress, SortDirection.ASCENDING),
LENGTH("Length", Long.class, MemoryBlockRow::getLength, SortDirection.ASCENDING);
<T> MemoryBlockTableColumns(String header, Class<T> cls, Function<MemoryBlockRow, T> getter,
SortDirection dir) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.dir = dir;
}
private final String header;
private final Function<MemoryBlockRow, ?> getter;
private final Class<?> cls;
private final SortDirection dir;
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(MemoryBlockRow row) {
return getter.apply(row);
}
@Override
public SortDirection defaultSortDirection() {
return dir;
}
}
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel =
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
GTable table;
GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
private Entry<Program, MemoryBlock> chosen;
protected DebuggerBlockChooserDialog() {
super("Memory Blocks", true, true, true, false);
populateComponents();
}
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
table = new GTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
panel.add(new JScrollPane(table));
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
panel.add(filterPanel, BorderLayout.SOUTH);
addWorkPanel(panel);
addOKButton();
addCancelButton();
table.getSelectionModel().addListSelectionListener(evt -> {
okButton.setEnabled(filterPanel.getSelectedItems().size() == 1);
// Prevent empty selection
});
// TODO: Adjust column widths?
TableColumnModel columnModel = table.getColumnModel();
TableColumn startCol = columnModel.getColumn(MemoryBlockTableColumns.START.ordinal());
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn endCol = columnModel.getColumn(MemoryBlockTableColumns.END.ordinal());
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lenCol = columnModel.getColumn(MemoryBlockTableColumns.LENGTH.ordinal());
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
}
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceSection section,
Collection<Program> programs) {
setBlocksFromPrograms(programs);
computeScores(section, tool.getService(DebuggerStaticMappingService.class));
selectHighestScoringBlock();
tool.showDialog(this);
return getChosen();
}
protected void computeScores(TraceSection section, DebuggerStaticMappingService service) {
for (MemoryBlockRow rec : tableModel.getModelData()) {
rec.score(section, service);
}
}
protected void setBlocksFromPrograms(Collection<Program> programs) {
this.tableModel.clear();
List<MemoryBlockRow> rows = new ArrayList<>();
for (Program program : programs) {
for (MemoryBlock block : program.getMemory().getBlocks()) {
rows.add(new MemoryBlockRow(program, block));
}
}
this.tableModel.addAll(rows);
}
protected void selectHighestScoringBlock() {
MemoryBlockRow best = null;
for (MemoryBlockRow rec : tableModel.getModelData()) {
if (best == null || rec.getScore() > best.getScore()) {
best = rec;
}
}
if (best != null) {
filterPanel.setSelectedItem(best);
}
}
@Override
protected void okCallback() {
MemoryBlockRow sel = filterPanel.getSelectedItem();
this.chosen = sel == null ? null : Map.entry(sel.program, sel.block);
close();
}
@Override
protected void cancelCallback() {
this.chosen = null;
close();
}
public Entry<Program, MemoryBlock> getChosen() {
return chosen;
}
}
@@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.util.Swing;
@@ -100,8 +101,8 @@ public class DebuggerModuleMapProposalDialog
protected static class ModuleMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<ModuleMapTableColumns, ModuleMapEntry> {
public ModuleMapPropsalTableModel() {
super("Module Map", ModuleMapTableColumns.class);
public ModuleMapPropsalTableModel(PluginTool tool) {
super(tool, "Module Map", ModuleMapTableColumns.class);
}
@Override
@@ -113,13 +114,13 @@ public class DebuggerModuleMapProposalDialog
private final DebuggerModulesProvider provider;
protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) {
super(MapModulesAction.NAME);
super(provider.getTool(), MapModulesAction.NAME);
this.provider = provider;
}
@Override
protected ModuleMapPropsalTableModel createTableModel() {
return new ModuleMapPropsalTableModel();
protected ModuleMapPropsalTableModel createTableModel(PluginTool tool) {
return new ModuleMapPropsalTableModel(tool);
}
@Override
@@ -53,8 +53,7 @@ import ghidra.async.TypeSpec;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
@@ -207,8 +206,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
public ModuleTableModel() {
super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new);
public ModuleTableModel(PluginTool tool) {
super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
ModuleRow::new);
}
@Override
@@ -221,8 +221,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
public SectionTableModel() {
super("Sections", SectionTableColumns.class, TraceSection::getObjectKey,
public SectionTableModel(PluginTool tool) {
super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
SectionRow::new);
}
@@ -555,11 +555,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
private final RecordersChangedListener recordersChangedListener =
new RecordersChangedListener();
protected final ModuleTableModel moduleTableModel = new ModuleTableModel();
protected final ModuleTableModel moduleTableModel;
protected GhidraTable moduleTable;
private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
protected final SectionTableModel sectionTableModel = new SectionTableModel();
protected final SectionTableModel sectionTableModel;
protected GhidraTable sectionTable;
protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
@@ -599,6 +599,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null);
this.plugin = plugin;
moduleTableModel = new ModuleTableModel(tool);
sectionTableModel = new SectionTableModel(tool);
setIcon(DebuggerResources.ICON_PROVIDER_MODULES);
setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES);
setWindowMenuGroup(DebuggerPluginPackage.NAME);
@@ -607,7 +610,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
blockChooserDialog = new DebuggerBlockChooserDialog();
blockChooserDialog = new DebuggerBlockChooserDialog(tool);
moduleProposalDialog = new DebuggerModuleMapProposalDialog(this);
sectionProposalDialog = new DebuggerSectionMapProposalDialog(this);
@@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction;
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
@@ -102,8 +103,8 @@ public class DebuggerSectionMapProposalDialog
protected static class SectionMapPropsalTableModel extends
DefaultEnumeratedColumnTableModel<SectionMapTableColumns, SectionMapEntry> {
public SectionMapPropsalTableModel() {
super("Section Map", SectionMapTableColumns.class);
public SectionMapPropsalTableModel(PluginTool tool) {
super(tool, "Section Map", SectionMapTableColumns.class);
}
@Override
@@ -115,13 +116,13 @@ public class DebuggerSectionMapProposalDialog
private final DebuggerModulesProvider provider;
public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) {
super(MapSectionsAction.NAME);
super(provider.getTool(), MapSectionsAction.NAME);
this.provider = provider;
}
@Override
protected SectionMapPropsalTableModel createTableModel() {
return new SectionMapPropsalTableModel();
protected SectionMapPropsalTableModel createTableModel(PluginTool tool) {
return new SectionMapPropsalTableModel(tool);
}
@Override
@@ -42,8 +42,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
@@ -103,9 +102,9 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> {
public MappingTableModel() {
super("Mappings", StaticMappingTableColumns.class, TraceStaticMapping::getObjectKey,
StaticMappingRow::new);
public MappingTableModel(PluginTool tool) {
super(tool, "Mappings", StaticMappingTableColumns.class,
TraceStaticMapping::getObjectKey, StaticMappingRow::new);
}
}
@@ -149,7 +148,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay();
protected final MappingTableModel mappingTableModel = new MappingTableModel();
protected final MappingTableModel mappingTableModel;
private JPanel mainPanel = new JPanel(new BorderLayout());
protected GTable mappingTable;
@@ -165,6 +164,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null);
this.plugin = plugin;
mappingTableModel = new MappingTableModel(tool);
this.addMappingDialog = new DebuggerAddMappingDialog();
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
@@ -647,7 +647,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
TargetObject targetObject = container.getTargetObject();
String name = targetObject.getName();
DefaultEnumeratedColumnTableModel<?, ObjectAttributeRow> model =
new DefaultEnumeratedColumnTableModel<>(name, ObjectAttributeColumn.class);
new DefaultEnumeratedColumnTableModel<>(tool, name, ObjectAttributeColumn.class);
Map<String, Object> map = container.getAttributeMap();
List<ObjectAttributeRow> list = new ArrayList<>();
for (Object val : map.values()) {
@@ -64,9 +64,7 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
protected RefreshAction actionRefresh;
protected JButton attachButton;
private final RowObjectTableModel<TargetAttachable> processes =
new DefaultEnumeratedColumnTableModel<>("Attachables",
AttachableProcessesTableColumns.class);
private final RowObjectTableModel<TargetAttachable> processes;
protected TargetAttacher attacher;
private GTable processTable;
@@ -74,6 +72,8 @@ public class DebuggerAttachDialog extends DialogComponentProvider {
super(AbstractAttachAction.NAME, true, true, true, false);
this.provider = provider;
this.plugin = provider.getPlugin();
processes = new DefaultEnumeratedColumnTableModel<>(plugin.getTool(), "Attachables",
AttachableProcessesTableColumns.class);
populateComponents();
createActions();
@@ -52,67 +52,6 @@ public class ObjectEnumeratedColumnTableModel<C extends ObjectsEnumeratedTableCo
}
}
public class TableRowIterator implements RowIterator<R> {
protected final ListIterator<R> it = modelData.listIterator();
protected int index;
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public R next() {
index = it.nextIndex();
return it.next();
}
@Override
public boolean hasPrevious() {
return it.hasPrevious();
}
@Override
public R previous() {
index = it.previousIndex();
return it.previous();
}
@Override
public int nextIndex() {
return it.nextIndex();
}
@Override
public int previousIndex() {
return it.previousIndex();
}
@Override
public void remove() {
it.remove();
fireTableRowsDeleted(index, index);
}
@Override
public void set(R e) {
it.set(e);
fireTableRowsUpdated(index, index);
}
@Override
public void notifyUpdated() {
fireTableRowsUpdated(index, index);
}
@Override
public void add(R e) {
it.add(e);
int nextIndex = it.nextIndex();
fireTableRowsInserted(nextIndex, nextIndex);
}
}
private final List<R> modelData = new ArrayList<>();
private final String name;
private C[] cols;
@@ -38,7 +38,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.pcode.UniqueRow.RefType;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.services.DebuggerEmulationService;
import ghidra.app.services.DebuggerTraceManagerService;
@@ -49,12 +49,10 @@ import ghidra.base.widgets.table.DataTypeTableCellEditor;
import ghidra.docking.settings.Settings;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeFrame;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
@@ -143,8 +141,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
protected static class PcodeTableModel
extends DefaultEnumeratedColumnTableModel<PcodeTableColumns, PcodeRow> {
public PcodeTableModel() {
super("p-code", PcodeTableColumns.class);
public PcodeTableModel(PluginTool tool) {
super(tool, "p-code", PcodeTableColumns.class);
}
@Override
@@ -208,8 +206,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
protected static class UniqueTableModel
extends DefaultEnumeratedColumnTableModel<UniqueTableColumns, UniqueRow> {
public UniqueTableModel() {
super("Unique", UniqueTableColumns.class);
public UniqueTableModel(PluginTool tool) {
super(tool, "Unique", UniqueTableColumns.class);
}
@Override
@@ -575,12 +573,12 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final UniqueTableModel uniqueTableModel;
GhidraTable uniqueTable;
UniqueTableModel uniqueTableModel = new UniqueTableModel();
GhidraTableFilterPanel<UniqueRow> uniqueFilterPanel;
final PcodeTableModel pcodeTableModel;
GhidraTable pcodeTable;
PcodeTableModel pcodeTableModel = new PcodeTableModel();
JLabel instructionLabel;
// No filter panel on p-code
PcodeCellRenderer codeColRenderer;
@@ -592,6 +590,9 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null);
this.plugin = plugin;
uniqueTableModel = new UniqueTableModel(tool);
pcodeTableModel = new PcodeTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
@@ -877,9 +878,10 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
pcodeTableModel.add(row);
}
protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
protected <T> void populateFromFrame(PcodeFrame frame, PcodeExecutorState<T> state,
PcodeArithmetic<T> arithmetic) {
populatePcode(frame);
populateUnique(frame, state);
populateUnique(frame, state, arithmetic);
}
protected int computeCodeColWidth(List<PcodeRow> rows) {
@@ -916,7 +918,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
pcodeTable.scrollToSelectedRow();
}
protected void populateUnique(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
protected <T> void populateUnique(PcodeFrame frame, PcodeExecutorState<T> state,
PcodeArithmetic<T> arithmetic) {
Language language = current.getTrace().getBaseLanguage();
// NOTE: They may overlap. I don't think I care.
Set<Varnode> uniques = new TreeSet<>(UNIQUE_COMPARATOR);
@@ -936,7 +939,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
// TODO: Permit modification of unique variables
List<UniqueRow> toAdd =
uniques.stream()
.map(u -> new UniqueRow(this, language, state, u))
.map(u -> new UniqueRow(this, language, state, arithmetic, u))
.collect(Collectors.toList());
uniqueTableModel.addAll(toAdd);
}
@@ -971,7 +974,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
populateSingleton(EnumPcodeRow.DECODE);
return;
}
DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time);
DebuggerPcodeMachine<?> emu = emulationService.getCachedEmulator(trace, time);
if (emu != null) {
clear();
doLoadPcodeFrameFromEmulator(emu);
@@ -986,8 +989,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
}, SwingExecutorService.LATER);
}
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
PcodeThread<byte[]> thread = emu.getThread(current.getThread().getPath(), false);
protected <T> void doLoadPcodeFrameFromEmulator(DebuggerPcodeMachine<T> emu) {
PcodeThread<T> thread = emu.getThread(current.getThread().getPath(), false);
if (thread == null) {
/**
* Happens when focus is on a thread not stepped in the schedule. Stepping it would
@@ -1012,7 +1015,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
populateSingleton(EnumPcodeRow.DECODE);
return;
}
populateFromFrame(frame, thread.getState());
populateFromFrame(frame, thread.getState(), thread.getArithmetic());
}
@AutoServiceConsumed
@@ -19,8 +19,9 @@ import java.math.BigInteger;
import java.util.stream.Stream;
import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
@@ -39,36 +40,54 @@ public class UniqueRow {
if (isWrite) {
return READ_WRITE;
}
else {
return READ;
}
return READ;
}
else {
if (isWrite) {
return WRITE;
}
else {
return NONE;
}
if (isWrite) {
return WRITE;
}
return NONE;
}
}
/**
* Putting these related methods, all using a common type, into a nested class allows us to
* introduce {@code <T>}, essentially a "universal type."
*
* @param <T> the type of state from which concrete parts are extracted.
*/
public static class ConcretizedState<T> {
private final PcodeExecutorState<T> state;
private final PcodeArithmetic<T> arithmetic;
public ConcretizedState(PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic) {
this.state = state;
this.arithmetic = arithmetic;
}
public byte[] getBytes(Varnode vn) {
return arithmetic.toConcrete(state.getVar(vn), Purpose.INSPECT);
}
public BigInteger getValue(Varnode vn) {
return arithmetic.toBigInteger(state.getVar(vn), Purpose.INSPECT);
}
}
protected final DebuggerPcodeStepperProvider provider;
protected final Language language;
protected final PcodeExecutorState<byte[]> state;
protected final ConcretizedState<?> state;
protected final Varnode vn;
protected DataType dataType;
public UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
PcodeExecutorState<byte[]> state, Varnode vn) {
public <T> UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
PcodeExecutorState<T> state, PcodeArithmetic<T> arithmetic, Varnode vn) {
if (!vn.isUnique()) {
throw new AssertionError("Only uniques allowed in unique table");
}
this.provider = provider;
this.language = language;
this.state = state;
this.state = new ConcretizedState<>(state, arithmetic);
this.vn = vn;
}
@@ -105,9 +124,26 @@ public class UniqueRow {
return String.format("$U%x:%d", vn.getOffset(), vn.getSize());
}
// TODO: Pluggable columns to display abstract pieces
/**
* Renders the raw bytes as space-separated hexadecimal-digit pairs, if concrete
*
* <p>
* If the state's concrete piece cannot be extracted by the machine's arithmetic, this simply
* returns {@code "(not concrete)"}.
*
* @return the byte string
*/
public String getBytes() {
// TODO: Could keep value cached?
byte[] bytes = state.getVar(vn);
byte[] bytes;
try {
bytes = state.getBytes(vn);
}
catch (UnsupportedOperationException e) {
return "(not concrete)";
}
if (bytes == null) {
return "??";
}
@@ -117,9 +153,18 @@ public class UniqueRow {
return NumericUtilities.convertBytesToString(bytes, " ");
}
/**
* Extract the concrete part of the variable as an unsigned big integer
*
* @return the value, or null if the value cannot be made concrete
*/
public BigInteger getValue() {
byte[] bytes = state.getVar(vn);
return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false);
try {
return state.getValue(vn);
}
catch (UnsupportedOperationException e) {
return null;
}
}
public DataType getDataType() {
@@ -135,7 +180,7 @@ public class UniqueRow {
if (dataType == null) {
return "";
}
byte[] bytes = state.getVar(vn);
byte[] bytes = state.getBytes(vn);
if (bytes == null) {
return "??";
}
@@ -203,8 +203,7 @@ public class DebuggerPlatformPlugin extends Plugin {
private final ChangeListener classChangeListener = evt -> this.classesChanged();
protected final DebuggerSelectPlatformOfferDialog offerDialog =
new DebuggerSelectPlatformOfferDialog();
protected final DebuggerSelectPlatformOfferDialog offerDialog;
final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>();
DockingAction actionMore;
@@ -212,6 +211,7 @@ public class DebuggerPlatformPlugin extends Plugin {
public DebuggerPlatformPlugin(PluginTool tool) {
super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
offerDialog = new DebuggerSelectPlatformOfferDialog(tool);
ClassSearcher.addChangeListener(classChangeListener);
@@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.table.GhidraTable;
@@ -134,8 +135,8 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
public static class OfferTableModel
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> {
public OfferTableModel() {
super("Offers", OfferTableColumns.class);
public OfferTableModel(PluginTool tool) {
super(tool, "Offers", OfferTableColumns.class);
}
@Override
@@ -146,14 +147,13 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
}
public static class OfferPanel extends JPanel {
private final OfferTableModel offerTableModel = new OfferTableModel();
private final GhidraTable offerTable = new GhidraTable(offerTableModel);
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel =
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
private final OfferTableModel offerTableModel;
private final GhidraTable offerTable;
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel;
private final JLabel descLabel = new JLabel();
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
private final JScrollPane scrollPane = new JScrollPane(offerTable) {
private final JScrollPane scrollPane = new JScrollPane() {
@Override
public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize();
@@ -178,7 +178,12 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
private LanguageID preferredLangID;
private CompilerSpecID preferredCsID;
{
protected OfferPanel(PluginTool tool) {
offerTableModel = new OfferTableModel(tool);
offerTable = new GhidraTable(offerTableModel);
offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel);
scrollPane.setViewportView(offerTable);
JPanel descPanel = new JPanel(new BorderLayout());
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
descPanel.add(descLabel, BorderLayout.CENTER);
@@ -263,13 +268,14 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
}
}
private final OfferPanel offerPanel = new OfferPanel();
private final OfferPanel offerPanel;
private boolean isCancelled = false;
protected DebuggerSelectPlatformOfferDialog() {
protected DebuggerSelectPlatformOfferDialog(PluginTool tool) {
super(DebuggerResources.NAME_CHOOSE_PLATFORM, true, false, true, false);
offerPanel = new OfferPanel(tool);
populateComponents();
}
@@ -340,4 +346,4 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
}
// Do nothing. Should be disabled anyway
}
}
}
@@ -32,6 +32,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.GTable;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.util.table.GhidraTableFilterPanel;
@@ -105,8 +106,8 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
protected static class AvailableRegistersTableModel extends
DefaultEnumeratedColumnTableModel<AvailableRegisterTableColumns, AvailableRegisterRow> {
public AvailableRegistersTableModel() {
super("Available Registers", AvailableRegisterTableColumns.class);
public AvailableRegistersTableModel(PluginTool tool) {
super(tool, "Available Registers", AvailableRegisterTableColumns.class);
}
@Override
@@ -119,8 +120,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
private Language language;
/* testing */ final AvailableRegistersTableModel availableTableModel =
new AvailableRegistersTableModel();
/* testing */ final AvailableRegistersTableModel availableTableModel;
private final Map<Register, AvailableRegisterRow> regMap = new HashMap<>();
private GTable availableTable;
@@ -135,6 +135,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider {
super(DebuggerResources.SelectRegistersAction.NAME, true, true, true, false);
this.provider = provider;
availableTableModel = new AvailableRegistersTableModel(provider.getTool());
populateComponents();
}
@@ -13,4 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
package ghidra.app.plugin.core.debug.gui.register;
import docking.widgets.table.DynamicTableColumn;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A factory for adding a custom column to the Registers table
*
* <p>
* All discovered factories' columns are automatically added as hidden columns to the Registers
* table.
*/
public interface DebuggerRegisterColumnFactory extends ExtensionPoint {
/**
* Create the column
*
* @return the column
*/
DynamicTableColumn<RegisterRow, ?, ?> create();
}
@@ -77,6 +77,7 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.*;
import ghidra.util.*;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
@@ -201,14 +202,24 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
protected static class RegistersTableModel
extends DefaultEnumeratedColumnTableModel<RegisterTableColumns, RegisterRow> {
public RegistersTableModel() {
super("Registers", RegisterTableColumns.class);
public RegistersTableModel(PluginTool tool) {
super(tool, "Registers", RegisterTableColumns.class);
}
@Override
public List<RegisterTableColumns> defaultSortOrder() {
return List.of(RegisterTableColumns.FAV, RegisterTableColumns.NUMBER);
}
@Override
protected TableColumnDescriptor<RegisterRow> createTableColumnDescriptor() {
TableColumnDescriptor<RegisterRow> descriptor = super.createTableColumnDescriptor();
for (DebuggerRegisterColumnFactory factory : ClassSearcher
.getInstances(DebuggerRegisterColumnFactory.class)) {
descriptor.addHiddenColumn(factory.create());
}
return descriptor;
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
@@ -472,8 +483,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
private JPanel mainPanel = new JPanel(new BorderLayout());
final RegistersTableModel regsTableModel;
GhidraTable regsTable;
RegistersTableModel regsTableModel = new RegistersTableModel();
GhidraTableFilterPanel<RegisterRow> regsFilterPanel;
Map<Register, RegisterRow> regMap = new HashMap<>();
@@ -495,6 +506,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
boolean isClone) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName());
this.plugin = plugin;
regsTableModel = new RegistersTableModel(tool);
this.selectionByCSpec = selectionByCSpec;
this.favoritesByCSpec = favoritesByCSpec;
this.isClone = isClone;
@@ -1387,4 +1401,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES));
}
}
public DebuggerCoordinates getCurrent() {
return current;
}
}
@@ -18,30 +18,49 @@ package ghidra.app.plugin.core.debug.gui.register;
import java.math.BigInteger;
import java.util.Objects;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Data;
import ghidra.trace.model.Trace;
import ghidra.util.Msg;
/**
* A row displayed in the registers table of the Debugger
*/
public class RegisterRow {
private final DebuggerRegistersProvider provider;
private boolean favorite;
private final int number;
private final Register register;
public RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
protected RegisterRow(DebuggerRegistersProvider provider, int number, Register register) {
this.provider = provider;
this.number = number;
this.register = Objects.requireNonNull(register);
this.favorite = provider.isFavorite(register);
}
/**
* Set whether this register is one of the user's favorites
*
* <p>
* Note: Favorites are memorized on a per-compiler-spec (ABI, almost) basis.
*
* @param favorite true if favorite
*/
public void setFavorite(boolean favorite) {
this.favorite = favorite;
provider.setFavorite(register, favorite);
}
/**
* Check if this register is one of the user's favorites
*
* @return true if favorite
*/
public boolean isFavorite() {
return favorite;
}
@@ -55,18 +74,42 @@ public class RegisterRow {
return number;
}
/**
* Get the register
*
* @return the register
*/
public Register getRegister() {
return register;
}
/**
* Get the register's name
*
* @return the name
*/
public String getName() {
return register.getName();
}
/**
* Check if the register can be edited
*
* @return true if editable
*/
public boolean isValueEditable() {
return provider.canWriteRegister(register);
}
/**
* Attempt to set the register's value
*
* <p>
* The edit will be directed according to the tool's current edit mode. See
* {@link DebuggerStateEditingService#getCurrentMode(Trace)}
*
* @param value the value
*/
public void setValue(BigInteger value) {
try {
provider.writeRegisterValue(register, value);
@@ -78,8 +121,13 @@ public class RegisterRow {
}
/**
* Get the value of the register
*
* <p>
* TODO: Perhaps some caching for all these getters which rely on the DB, since they could be
* invoked on every repaint.
*
* @return the value
*/
public BigInteger getValue() {
return provider.getRegisterValue(register);
@@ -89,31 +137,78 @@ public class RegisterRow {
return provider.getRegisterData(register);
}
/**
* Assign a data type to the register
*
* <p>
* This is memorized in the trace for the current and future snaps
*
* @param dataType the data type
*/
public void setDataType(DataType dataType) {
provider.writeRegisterDataType(register, dataType);
}
/**
* Get the data type of the register
*
* @return the data type
*/
public DataType getDataType() {
return provider.getRegisterDataType(register);
}
/**
* Set the value of the register as represented by its data type
*
* @param representation the value to set
*/
public void setRepresentation(String representation) {
provider.writeRegisterValueRepresentation(register, representation);
}
/**
* Check if the register's value can be set via its data type's representation
*
* @return
*/
public boolean isRepresentationEditable() {
return provider.canWriteRegisterRepresentation(register);
}
/**
* Get the value of the register as represented by its data type
*
* @return the value
*/
public String getRepresentation() {
return provider.getRegisterValueRepresentation(register);
}
/**
* Check if the register's value is (completely) known
*
* @return true if known
*/
public boolean isKnown() {
return provider.isRegisterKnown(register);
}
/**
* Check if the register's value changed since last navigation or command
*
* @return true if changed
*/
public boolean isChanged() {
return provider.isRegisterChanged(register);
}
/**
* Get the table's current coordinates (usually also the tool's)
*
* @return the coordinates
*/
public DebuggerCoordinates getCurrent() {
return provider.getCurrent();
}
}
@@ -36,8 +36,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.*;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
@@ -116,8 +115,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
protected static class StackTableModel
extends DefaultEnumeratedColumnTableModel<StackTableColumns, StackFrameRow> {
public StackTableModel() {
super("Stack", StackTableColumns.class);
public StackTableModel(PluginTool tool) {
super(tool, "Stack", StackTableColumns.class);
}
@Override
@@ -243,7 +242,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
protected final StackTableModel stackTableModel = new StackTableModel();
protected final StackTableModel stackTableModel;
protected GhidraTable stackTable;
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
@@ -253,7 +252,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
public DebuggerStackProvider(DebuggerStackPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName());
//this.plugin = plugin;
stackTableModel = new StackTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
@@ -92,8 +92,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
public ThreadTableModel(DebuggerThreadsProvider provider) {
super("Threads", ThreadTableColumns.class, TraceThread::getObjectKey,
t -> new ThreadRow(provider.modelService, t));
super(provider.getTool(), "Threads", ThreadTableColumns.class,
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
}
}
@@ -30,6 +30,7 @@ import com.google.common.collect.Collections2;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
@@ -130,8 +131,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
}
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
protected boolean hideScratch = true;
@@ -141,8 +141,10 @@ public class DebuggerSnapshotTablePanel extends JPanel {
protected final SnapshotListener listener = new SnapshotListener();
public DebuggerSnapshotTablePanel() {
public DebuggerSnapshotTablePanel(PluginTool tool) {
super(new BorderLayout());
snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>(tool, "Snapshots", SnapshotTableColumns.class);
snapshotTable = new GTable(snapshotTableModel);
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(snapshotTable));
@@ -60,7 +60,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
/*testing*/ final DebuggerSnapshotTablePanel mainPanel;
private DebuggerSnapActionContext myActionContext;
@@ -80,6 +80,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
setHelpLocation(HELP_PROVIDER_TIME);
setWindowMenuGroup(DebuggerPluginPackage.NAME);
mainPanel = new DebuggerSnapshotTablePanel(tool);
buildMainPanel();
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
@@ -86,7 +86,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1)));
{
snapshotPanel = new DebuggerSnapshotTablePanel();
snapshotPanel = new DebuggerSnapshotTablePanel(tool);
workPanel.add(snapshotPanel, BorderLayout.CENTER);
}
@@ -197,8 +197,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
protected static class WatchTableModel
extends DefaultEnumeratedColumnTableModel<WatchTableColumns, WatchRow> {
public WatchTableModel() {
super("Watches", WatchTableColumns.class);
public WatchTableModel(PluginTool tool) {
super(tool, "Watches", WatchTableColumns.class);
}
}
@@ -346,7 +346,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private JPanel mainPanel = new JPanel(new BorderLayout());
protected final WatchTableModel watchTableModel = new WatchTableModel();
protected final WatchTableModel watchTableModel;
protected GhidraTable watchTable;
protected GhidraTableFilterPanel<WatchRow> watchFilterPanel;
@@ -366,6 +366,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName());
this.plugin = plugin;
watchTableModel = new WatchTableModel(tool);
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
@@ -31,7 +31,7 @@ import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
@@ -170,21 +170,20 @@ public class WatchRow {
return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length);
}
public static class ReadDepsTraceBytesPcodeExecutorState
extends TraceBytesPcodeExecutorState {
public static class ReadDepsTraceBytesPcodeExecutorStatePiece
extends DirectBytesTracePcodeExecutorStatePiece {
private AddressSet reads = new AddressSet();
public ReadDepsTraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace, snap, thread, frame);
}
@Override
public byte[] getVar(AddressSpace space, long offset, int size,
boolean truncateAddressableUnit) {
byte[] data = super.getVar(space, offset, size, truncateAddressableUnit);
public byte[] getVar(AddressSpace space, long offset, int size, boolean quantize) {
byte[] data = super.getVar(space, offset, size, quantize);
if (space.isMemorySpace()) {
offset = truncateOffset(space, offset);
offset = quantizeOffset(space, offset);
}
if (space.isMemorySpace() || space.isRegisterSpace()) {
try {
@@ -213,42 +212,42 @@ public class WatchRow {
public static class ReadDepsPcodeExecutor
extends PcodeExecutor<Pair<byte[], Address>> {
private ReadDepsTraceBytesPcodeExecutorState depsState;
private ReadDepsTraceBytesPcodeExecutorStatePiece depsPiece;
public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorState depsState,
public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorStatePiece depsState,
SleighLanguage language, PairedPcodeArithmetic<byte[], Address> arithmetic,
PcodeExecutorState<Pair<byte[], Address>> state) {
super(language, arithmetic, state);
this.depsState = depsState;
this.depsPiece = depsState;
}
@Override
public PcodeFrame execute(PcodeProgram program,
PcodeUseropLibrary<Pair<byte[], Address>> library) {
depsState.reset();
depsPiece.reset();
return super.execute(program, library);
}
public AddressSet getReads() {
return depsState.getReads();
return depsPiece.getReads();
}
}
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(
DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
ReadDepsTraceBytesPcodeExecutorState state =
new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
ReadDepsTraceBytesPcodeExecutorStatePiece piece =
new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame());
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Watch expressions require a SLEIGH language");
}
PcodeExecutorState<Pair<byte[], Address>> paired =
state.paired(new AddressOfPcodeExecutorState(language.isBigEndian()));
PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
.paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian()));
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE);
return new ReadDepsPcodeExecutor(state, (SleighLanguage) language, arithmetic, paired);
return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired);
}
public void setCoordinates(DebuggerCoordinates coordinates) {
@@ -271,7 +270,7 @@ public class WatchRow {
recompile();
}
if (coordinates.isAliveAndReadsPresent()) {
asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates);
asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates);
}
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
@@ -20,7 +20,7 @@ import java.util.concurrent.*;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
@@ -31,8 +31,17 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
public abstract class AbstractReadsTargetPcodeExecutorState
extends TraceCachedWriteBytesPcodeExecutorState {
/**
* An executor state piece that knows to read live state if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the desired state, if they're
* not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to 1
* second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*/
public abstract class AbstractReadsTargetPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
@@ -86,30 +95,47 @@ public abstract class AbstractReadsTargetPcodeExecutorState
protected final TraceRecorder recorder;
protected final PluginTool tool;
public AbstractReadsTargetPcodeExecutorState(PluginTool tool, Trace trace, long snap,
public AbstractReadsTargetPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(trace, snap, thread, frame);
this.tool = tool;
this.recorder = recorder;
}
protected abstract AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms);
/**
* Get the tool that manages this state's emulator.
*
* <p>
* This is necessary to obtain the static mapping service, in case memory should be filled from
* static images.
*
* @return the tool
*/
public PluginTool getTool() {
return tool;
}
@Override
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> {
TraceMemorySpace tms;
if (s.isUniqueSpace()) {
tms = null;
/**
* Get the recorder associated with the trace
*
* @return this is used to check for and perform live reads
*/
public TraceRecorder getRecorder() {
return recorder;
}
/**
* A partially implemented space map which retrieves "backing" objects from the trace's memory
* and register spaces.
*/
protected abstract class TargetBackedSpaceMap
extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space")) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
}
else {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space")) {
tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true);
}
}
return createCachedSpace(s, tms);
});
}
}
}
@@ -17,67 +17,70 @@ package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.BytesPcodeThread;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.trace.TracePcodeEmulator;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.pcode.emu.*;
import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A trace emulator that knows how to read target memory when necessary
*
* <p>
* This is the default emulator used by the Debugger UI to perform interpolation and extrapolation.
* For standalone scripting, consider using {@link BytesTracePcodeEmulator} or {@link PcodeEmulator}
* instead. The former readily reads and records its state to traces, while the latter is the
* simplest use case. See scripts ending in {@code EmuExampleScript} for example uses.
*
* <p>
* This emulator must always be run in its own thread, or at least a thread that can never lock the
* UI. It blocks on target reads so that execution can proceed synchronously. Probably the most
* suitable option is to use a background task.
*/
public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator
implements DebuggerPcodeMachine<byte[]> {
protected final PluginTool tool;
protected final TraceRecorder recorder;
public DebuggerTracePcodeEmulator(PluginTool tool, Trace trace, long snap,
/**
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the trace from which the emulator loads state
* @param snap the snap from which the emulator loads state
* @param recorder if applicable, the recorder for the trace's live target
*/
public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
super(trace, snap);
this.tool = tool;
this.recorder = recorder;
}
protected boolean isRegisterKnown(String threadName, Register register) {
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName);
TraceMemoryRegisterSpace space =
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
if (space == null) {
return false;
}
return space.getState(snap, register) == TraceMemoryState.KNOWN;
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
protected BytesPcodeThread createThread(String name) {
BytesPcodeThread thread = super.createThread(name);
Register contextreg = language.getContextBaseRegister();
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) {
RegisterValue context = trace.getRegisterContextManager()
.getValueWithDefault(language, contextreg, snap, thread.getCounter());
if (context != null) { // TODO: Why does this happen?
thread.overrideContext(context);
}
}
initializeThreadContext(thread);
return thread;
}
@Override
protected PcodeExecutorState<byte[]> createSharedState() {
public TracePcodeExecutorState<byte[]> createSharedState() {
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder);
}
@Override
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,
@@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
/**
* The Debugger's default emulator factory
*/
public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory {
// TODO: Config options:
// 1) userop library
@Override
public String getTitle() {
return "Default Concrete P-code Emulator";
}
@Override
public DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder);
}
}
@@ -17,14 +17,19 @@ package ghidra.app.plugin.core.debug.service.emulation;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.Range;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent;
@@ -32,8 +37,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*;
@@ -49,6 +53,7 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.CompareResult;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
@@ -74,20 +79,21 @@ import ghidra.util.task.TaskMonitor;
})
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
protected static final int MAX_CACHE_SIZE = 5;
protected static long nextSnap = Long.MIN_VALUE; // HACK
protected static class CacheKey implements Comparable<CacheKey> {
protected final Trace trace;
protected final TraceSchedule time;
private final int hashCode;
public CacheKey(Trace trace, TraceSchedule time) {
this.trace = trace;
this.time = time;
this.trace = Objects.requireNonNull(trace);
this.time = Objects.requireNonNull(time);
this.hashCode = Objects.hash(trace, time);
}
@Override
public int hashCode() {
return Objects.hash(trace, time);
return hashCode;
}
@Override
@@ -130,9 +136,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
protected static class CachedEmulator {
final DebuggerTracePcodeEmulator emulator;
final DebuggerPcodeMachine<?> emulator;
public CachedEmulator(DebuggerTracePcodeEmulator emulator) {
public CachedEmulator(DebuggerPcodeMachine<?> emulator) {
this.emulator = emulator;
}
}
@@ -162,6 +168,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
protected DebuggerPcodeEmulatorFactory emulatorFactory =
new BytesDebuggerPcodeEmulatorFactory();
protected final Set<CacheKey> eldest = new LinkedHashSet<>();
protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>();
protected final AsyncLazyMap<CacheKey, Long> requests =
@@ -180,6 +189,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
DockingAction actionEmulateProgram;
DockingAction actionEmulateAddThread;
Map<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> //
actionsChooseEmulatorFactory = new HashMap<>();
final ChangeListener classChangeListener = this::classesChanged;
public DebuggerEmulationServicePlugin(PluginTool tool) {
super(tool);
@@ -205,6 +218,46 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
.popupWhen(this::emulateAddThreadEnabled)
.onAction(this::emulateAddThreadActivated)
.buildAndInstall(tool);
ClassSearcher.addChangeListener(classChangeListener);
updateConfigureEmulatorStates();
}
private void classesChanged(ChangeEvent e) {
updateConfigureEmulatorStates();
}
private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) {
ToggleDockingAction action = ConfigureEmulatorAction.builder(this)
.menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle())
.onAction(ctx -> configureEmulatorActivated(factory))
.buildAndInstall(tool);
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), "zz");
return action;
}
private void updateConfigureEmulatorStates() {
Map<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> byClass =
getEmulatorFactories().stream()
.collect(Collectors.toMap(DebuggerPcodeEmulatorFactory::getClass,
Objects::requireNonNull));
Iterator<Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction>> it =
actionsChooseEmulatorFactory.entrySet().iterator();
while (it.hasNext()) {
Entry<Class<? extends DebuggerPcodeEmulatorFactory>, ToggleDockingAction> ent =
it.next();
if (!byClass.keySet().contains(ent.getKey())) {
tool.removeAction(ent.getValue());
}
}
for (Entry<Class<? extends DebuggerPcodeEmulatorFactory>, DebuggerPcodeEmulatorFactory> ent : byClass
.entrySet()) {
if (!actionsChooseEmulatorFactory.containsKey(ent.getKey())) {
ToggleDockingAction action = createActionChooseEmulator(ent.getValue());
action.setSelected(ent.getKey() == emulatorFactory.getClass());
actionsChooseEmulatorFactory.put(ent.getKey(), action);
}
}
}
private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) {
@@ -228,7 +281,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = null;
try {
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
}
@@ -275,7 +327,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
private void emulateAddThreadActivated(ProgramLocationActionContext ctx) {
Program programOrView = ctx.getProgram();
if (programOrView instanceof TraceProgramView) {
TraceProgramView view = (TraceProgramView) programOrView;
@@ -322,6 +373,35 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
}
private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) {
// TODO: Pull up config page. Tool Options? Program/Trace Options?
setEmulatorFactory(factory);
}
@Override
public Collection<DebuggerPcodeEmulatorFactory> getEmulatorFactories() {
return ClassSearcher.getInstances(DebuggerPcodeEmulatorFactory.class);
}
@Override
public synchronized void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory) {
emulatorFactory = Objects.requireNonNull(factory);
for (ToggleDockingAction toggle : actionsChooseEmulatorFactory.values()) {
toggle.setSelected(false);
}
ToggleDockingAction chosen = actionsChooseEmulatorFactory.get(factory.getClass());
if (chosen == null) {
// Must be special or otherwise not discovered. Could happen.
Msg.warn(this, "An undiscovered emulator factory was set via the API: " + factory);
}
chosen.setSelected(true);
}
@Override
public synchronized DebuggerPcodeEmulatorFactory getEmulatorFactory() {
return emulatorFactory;
}
protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
synchronized (cache) {
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
@@ -378,7 +458,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
Trace trace = key.trace;
TraceSchedule time = key.time;
CachedEmulator ce;
DebuggerTracePcodeEmulator emu;
DebuggerPcodeMachine<?> emu;
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
if (ancestor != null) {
CacheKey prevKey = ancestor.getKey();
@@ -396,7 +476,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
time.finish(trace, prevKey.time, emu, monitor);
}
else {
emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(),
emu = emulatorFactory.create(tool, trace, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace));
ce = new CachedEmulator(emu);
monitor.initialize(time.totalTickCount());
@@ -405,7 +485,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
TraceSnapshot destSnap;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
destSnap = findScratch(trace, time);
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
emu.writeDown(trace, destSnap.getKey(), time.getSnap());
}
synchronized (cache) {
@@ -436,7 +516,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
}
@Override
public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) {
public DebuggerPcodeMachine<?> getCachedEmulator(Trace trace, TraceSchedule time) {
CachedEmulator ce = cache.get(new CacheKey(trace, time));
return ce == null ? null : ce.emulator;
}
@@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A factory for configuring and creating a Debugger-integrated emulator
*
* <p>
* See {@link BytesDebuggerPcodeEmulatorFactory} for the default implementation. See the Taint
* Analyzer for the archetype of alternative implementations.
*/
public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
// TODO: Config options, use ModelFactory as a model
/**
* Get the title, to appear in menus and dialogs
*
* @return the title
*/
String getTitle();
/**
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the user's current trace from which the emulator should load state
* @param snap the user's current snap from which the emulator should load state
* @param recorder if applicable, the recorder for the trace's live target
* @return the emulator
*/
DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder);
}
@@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeMachine;
/**
* A Debugger-integrated emulator (or p-code machine)
*
* <p>
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
* developers should use this interface, but emulator clients should not. Clients should use
* {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some
* auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and
* {@link AuxDebuggerEmulatorPartsFactory}.
*
* @param <T> the type of values in the machine's memory and registers
*/
public interface DebuggerPcodeMachine<T> extends TracePcodeMachine<T> {
/**
* Get the tool where this emulator is integrated
*
* @return the tool
*/
PluginTool getTool();
/**
* Get the trace's recorder for its live target, if applicable
*
* @return the recorder, or null
*/
TraceRecorder getRecorder();
}
@@ -15,123 +15,29 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Collection;
import java.util.Map.Entry;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
public class ReadsTargetMemoryPcodeExecutorState
extends AbstractReadsTargetPcodeExecutorState {
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
AddressSet unknown;
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (fillUnknownWithRecorder(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (fillUnknownWithStaticImages(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
if (!isLive()) {
return false;
}
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true;
}
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, unknown, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange srng = mappedRng.getSourceAddressRange();
long shift = mappedRng.getShift();
for (AddressRange subsrng : initialized.intersectRange(srng.getMinAddress(),
srng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subsrng);
long lower = subsrng.getMinAddress().getOffset();
long fullLen = subsrng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
int read =
memory.getBytes(space.getAddress(lower), data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subsrng + ". Got " + read +
" bytes");
}
// write(lower - shift, data, 0 ,read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
}
/**
* A state composing a single {@link ReadsTargetMemoryPcodeExecutorStatePiece}
*/
public class ReadsTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread probably null, since this the shared part
* @param frame probably 0, because frame only matters for non-null thread
* @param recorder the recorder of the emulator
*/
public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetMemoryCachedSpace(language, s, tms, snap);
super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}
@@ -0,0 +1,173 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the block(s) containing the
* read, if they're not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state
* will wait up to 1 second (see
* {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <p>
* This state will also attempt to fill unknown bytes with values from mapped static images. The
* order to retrieve state is:
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* <li>Mapped static images, if available</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetMemoryPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a memory space, of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
AddressSet unknown;
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (fillUnknownWithRecorder(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (fillUnknownWithStaticImages(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
if (!isLive()) {
return false;
}
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true;
}
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, unknown, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange drng = mappedRng.getDestinationAddressRange();
long shift = mappedRng.getShift();
for (AddressRange subdrng : initialized.intersectRange(drng.getMinAddress(),
drng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subdrng);
long lower = subdrng.getMinAddress().getOffset();
long fullLen = subdrng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
int read =
memory.getBytes(space.getAddress(lower), data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subdrng + ". Got " + read +
" bytes");
}
// write(lower - shift, data, 0 ,read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
}
public ReadsTargetMemoryPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetMemoryCachedSpace(language, space, backing, snap);
}
};
}
}
@@ -15,63 +15,29 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.HashSet;
import java.util.Set;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
public class ReadsTargetRegistersPcodeExecutorState
extends AbstractReadsTargetPcodeExecutorState {
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
Set<Register> toRead = new HashSet<>();
for (AddressRange rng : unknown) {
Register register =
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + rng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
}
}
/**
* A state composing a single {@link ReadsTargetRegistersPcodeExecutorStatePiece}
*/
public class ReadsTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread the thread to which the state is assigned
* @param frame the frame to which the state is assigned, probably 0
* @param recorder the recorder of the emulator
*/
public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
TraceMemorySpace tms) {
return new ReadsTargetRegistersCachedSpace(language, s, tms, snap);
super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}
@@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the register to be read, if
* it's not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to
* 1 second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetRegistersPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a register space (really a thread) of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
Set<Register> toRead = new HashSet<>();
for (AddressRange rng : unknown) {
Register register =
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + rng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
}
}
public ReadsTargetRegistersPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetRegistersCachedSpace(language, space, backing, snap);
}
};
}
}

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