mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 11:37:00 +08:00
Merge remote-tracking branch 'origin/GP-1230_Dan_taint-REBASED-SQUASHED'
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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>
|
||||
|
||||
+4
-3
@@ -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());
|
||||
|
||||
+4
-3
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -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?
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+6
-5
@@ -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);
|
||||
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
|
||||
+6
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -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
|
||||
|
||||
+7
-6
@@ -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);
|
||||
|
||||
-243
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -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
|
||||
|
||||
+12
-9
@@ -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);
|
||||
|
||||
|
||||
+6
-5
@@ -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
|
||||
|
||||
+7
-6
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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()) {
|
||||
|
||||
+3
-3
@@ -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();
|
||||
|
||||
-61
@@ -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;
|
||||
|
||||
+22
-19
@@ -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
|
||||
|
||||
+64
-19
@@ -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 "??";
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
|
||||
+17
-11
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -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();
|
||||
}
|
||||
+21
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+96
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -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));
|
||||
|
||||
+2
-1
@@ -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());
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
|
||||
+18
-19
@@ -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());
|
||||
|
||||
+46
-20
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-28
@@ -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,
|
||||
+39
@@ -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);
|
||||
}
|
||||
}
|
||||
+94
-14
@@ -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;
|
||||
}
|
||||
|
||||
+51
@@ -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);
|
||||
}
|
||||
+51
@@ -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();
|
||||
}
|
||||
+17
-111
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+173
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+17
-51
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+108
@@ -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
Reference in New Issue
Block a user