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

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

View file

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

View file

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

View file

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

View file

@ -572,6 +572,21 @@ public interface DebuggerResources {
}
}
interface ConfigureEmulatorAction {
String NAME = "Configure Emulator";
String DESCRIPTION = "Choose and configure the current emulator";
String GROUP = GROUP_MAINTENANCE;
String HELP_ANCHOR = "configure_emulator";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
abstract class AbstractQuickLaunchAction extends DockingAction {
public static final String NAME = "Quick Launch";
public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon?

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,243 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.modules;
import java.awt.BorderLayout;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.modules.TraceSection;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerBlockChooserDialog extends DialogComponentProvider {
static class MemoryBlockRow {
private final Program program;
private final MemoryBlock block;
private double score;
public MemoryBlockRow(Program program, MemoryBlock block) {
this.program = program;
this.block = block;
}
public Program getProgram() {
return program;
}
public MemoryBlock getBlock() {
return block;
}
public String getProgramName() {
DomainFile df = program.getDomainFile();
if (df != null) {
return df.getName();
}
return program.getName();
}
public String getBlockName() {
return block.getName();
}
public Address getMinAddress() {
return block.getStart();
}
public Address getMaxAddress() {
return block.getEnd();
}
public long getLength() {
return block.getSize();
}
public double getScore() {
return score;
}
public double score(TraceSection section, DebuggerStaticMappingService service) {
if (section == null) {
return score = 0;
}
return score = service.proposeSectionMap(section, program, block).computeScore();
}
public ProgramLocation getProgramLocation() {
return new ProgramLocation(program, block.getStart());
}
}
enum MemoryBlockTableColumns
implements EnumeratedTableColumn<MemoryBlockTableColumns, MemoryBlockRow> {
SCORE("Score", Double.class, MemoryBlockRow::getScore, SortDirection.DESCENDING),
PROGRAM("Program", String.class, MemoryBlockRow::getProgramName, SortDirection.ASCENDING),
BLOCK("Block", String.class, MemoryBlockRow::getBlockName, SortDirection.ASCENDING),
START("Start Address", Address.class, MemoryBlockRow::getMinAddress, SortDirection.ASCENDING),
END("End Address", Address.class, MemoryBlockRow::getMaxAddress, SortDirection.ASCENDING),
LENGTH("Length", Long.class, MemoryBlockRow::getLength, SortDirection.ASCENDING);
<T> MemoryBlockTableColumns(String header, Class<T> cls, Function<MemoryBlockRow, T> getter,
SortDirection dir) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.dir = dir;
}
private final String header;
private final Function<MemoryBlockRow, ?> getter;
private final Class<?> cls;
private final SortDirection dir;
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(MemoryBlockRow row) {
return getter.apply(row);
}
@Override
public SortDirection defaultSortDirection() {
return dir;
}
}
final EnumeratedColumnTableModel<MemoryBlockRow> tableModel =
new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class);
GTable table;
GhidraTableFilterPanel<MemoryBlockRow> filterPanel;
private Entry<Program, MemoryBlock> chosen;
protected DebuggerBlockChooserDialog() {
super("Memory Blocks", true, true, true, false);
populateComponents();
}
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
table = new GTable(tableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
panel.add(new JScrollPane(table));
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
panel.add(filterPanel, BorderLayout.SOUTH);
addWorkPanel(panel);
addOKButton();
addCancelButton();
table.getSelectionModel().addListSelectionListener(evt -> {
okButton.setEnabled(filterPanel.getSelectedItems().size() == 1);
// Prevent empty selection
});
// TODO: Adjust column widths?
TableColumnModel columnModel = table.getColumnModel();
TableColumn startCol = columnModel.getColumn(MemoryBlockTableColumns.START.ordinal());
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn endCol = columnModel.getColumn(MemoryBlockTableColumns.END.ordinal());
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lenCol = columnModel.getColumn(MemoryBlockTableColumns.LENGTH.ordinal());
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
}
public Map.Entry<Program, MemoryBlock> chooseBlock(PluginTool tool, TraceSection section,
Collection<Program> programs) {
setBlocksFromPrograms(programs);
computeScores(section, tool.getService(DebuggerStaticMappingService.class));
selectHighestScoringBlock();
tool.showDialog(this);
return getChosen();
}
protected void computeScores(TraceSection section, DebuggerStaticMappingService service) {
for (MemoryBlockRow rec : tableModel.getModelData()) {
rec.score(section, service);
}
}
protected void setBlocksFromPrograms(Collection<Program> programs) {
this.tableModel.clear();
List<MemoryBlockRow> rows = new ArrayList<>();
for (Program program : programs) {
for (MemoryBlock block : program.getMemory().getBlocks()) {
rows.add(new MemoryBlockRow(program, block));
}
}
this.tableModel.addAll(rows);
}
protected void selectHighestScoringBlock() {
MemoryBlockRow best = null;
for (MemoryBlockRow rec : tableModel.getModelData()) {
if (best == null || rec.getScore() > best.getScore()) {
best = rec;
}
}
if (best != null) {
filterPanel.setSelectedItem(best);
}
}
@Override
protected void okCallback() {
MemoryBlockRow sel = filterPanel.getSelectedItem();
this.chosen = sel == null ? null : Map.entry(sel.program, sel.block);
close();
}
@Override
protected void cancelCallback() {
this.chosen = null;
close();
}
public Entry<Program, MemoryBlock> getChosen() {
return chosen;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -52,67 +52,6 @@ public class ObjectEnumeratedColumnTableModel<C extends ObjectsEnumeratedTableCo
}
}
public class TableRowIterator implements RowIterator<R> {
protected final ListIterator<R> it = modelData.listIterator();
protected int index;
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public R next() {
index = it.nextIndex();
return it.next();
}
@Override
public boolean hasPrevious() {
return it.hasPrevious();
}
@Override
public R previous() {
index = it.previousIndex();
return it.previous();
}
@Override
public int nextIndex() {
return it.nextIndex();
}
@Override
public int previousIndex() {
return it.previousIndex();
}
@Override
public void remove() {
it.remove();
fireTableRowsDeleted(index, index);
}
@Override
public void set(R e) {
it.set(e);
fireTableRowsUpdated(index, index);
}
@Override
public void notifyUpdated() {
fireTableRowsUpdated(index, index);
}
@Override
public void add(R e) {
it.add(e);
int nextIndex = it.nextIndex();
fireTableRowsInserted(nextIndex, nextIndex);
}
}
private final List<R> modelData = new ArrayList<>();
private final String name;
private C[] cols;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
/**
* The Debugger's default emulator factory
*/
public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory {
// TODO: Config options:
// 1) userop library
@Override
public String getTitle() {
return "Default Concrete P-code Emulator";
}
@Override
public DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder);
}
}

View file

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

View file

@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A factory for configuring and creating a Debugger-integrated emulator
*
* <p>
* See {@link BytesDebuggerPcodeEmulatorFactory} for the default implementation. See the Taint
* Analyzer for the archetype of alternative implementations.
*/
public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
// TODO: Config options, use ModelFactory as a model
/**
* Get the title, to appear in menus and dialogs
*
* @return the title
*/
String getTitle();
/**
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the user's current trace from which the emulator should load state
* @param snap the user's current snap from which the emulator should load state
* @param recorder if applicable, the recorder for the trace's live target
* @return the emulator
*/
DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder);
}

View file

@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeMachine;
/**
* A Debugger-integrated emulator (or p-code machine)
*
* <p>
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
* developers should use this interface, but emulator clients should not. Clients should use
* {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some
* auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and
* {@link AuxDebuggerEmulatorPartsFactory}.
*
* @param <T> the type of values in the machine's memory and registers
*/
public interface DebuggerPcodeMachine<T> extends TracePcodeMachine<T> {
/**
* Get the tool where this emulator is integrated
*
* @return the tool
*/
PluginTool getTool();
/**
* Get the trace's recorder for its live target, if applicable
*
* @return the recorder, or null
*/
TraceRecorder getRecorder();
}

View file

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

View file

@ -0,0 +1,173 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the block(s) containing the
* read, if they're not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state
* will wait up to 1 second (see
* {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <p>
* This state will also attempt to fill unknown bytes with values from mapped static images. The
* order to retrieve state is:
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* <li>Mapped static images, if available</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetMemoryPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a memory space, of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
AddressSet unknown;
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (fillUnknownWithRecorder(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (fillUnknownWithStaticImages(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
if (!isLive()) {
return false;
}
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true;
}
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, unknown, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange drng = mappedRng.getDestinationAddressRange();
long shift = mappedRng.getShift();
for (AddressRange subdrng : initialized.intersectRange(drng.getMinAddress(),
drng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subdrng);
long lower = subdrng.getMinAddress().getOffset();
long fullLen = subdrng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
int read =
memory.getBytes(space.getAddress(lower), data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subdrng + ". Got " + read +
" bytes");
}
// write(lower - shift, data, 0 ,read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
}
public ReadsTargetMemoryPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetMemoryCachedSpace(language, space, backing, snap);
}
};
}
}

View file

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

View file

@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.emulation;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the register to be read, if
* it's not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to
* 1 second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetRegistersPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a register space (really a thread) of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
Set<Register> toRead = new HashSet<>();
for (AddressRange rng : unknown) {
Register register =
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + rng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
}
}
public ReadsTargetRegistersPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetRegistersCachedSpace(language, space, backing, snap);
}
};
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,154 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
/**
* An executor state which can asynchronously read and write a live target, if applicable
*
* <p>
* This is used for executing Sleigh code to manipulate trace history or a live target.
*
* <p>
* TODO: It might be easier to re-factor this to operate synchronously, executing Sleigh programs in
* a separate thread.
*/
public class TraceRecorderAsyncPcodeExecutorStatePiece
extends AsyncWrappedPcodeExecutorStatePiece<byte[], byte[]> {
private final TraceRecorder recorder;
private final DirectBytesTracePcodeExecutorStatePiece traceState;
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState;
public TraceRecorderAsyncPcodeExecutorStatePiece(TraceRecorder recorder, long snap,
TraceThread thread, int frame) {
super(
new DirectBytesTracePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame));
this.recorder = recorder;
this.traceState = (DirectBytesTracePcodeExecutorStatePiece) state;
this.traceMemState =
new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame);
}
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
boolean quantize, byte[] val) {
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
}
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {
Address floor = map.floorKey(addr);
NavigableMap<Address, byte[]> tail;
if (floor == null) {
tail = map;
}
else {
tail = map.tailMap(floor, true);
}
byte[] result = new byte[size];
for (Map.Entry<Address, byte[]> ent : tail.entrySet()) {
long off = ent.getKey().subtract(addr);
if (off >= size || off < 0) {
break;
}
int subSize = Math.min(size - (int) off, ent.getValue().length);
System.arraycopy(ent.getValue(), 0, result, (int) off, subSize);
}
return result;
}
protected CompletableFuture<byte[]> doGetTargetVar(AddressSpace space, long offset,
int size, boolean quantize) {
if (space.isMemorySpace()) {
Address addr = space.getAddress(quantizeOffset(space, offset));
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
CompletableFuture<NavigableMap<Address, byte[]>> future =
recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
return future.thenApply(map -> {
return knitFromResults(map, addr, size);
});
}
assert space.isRegisterSpace();
Language lang = recorder.getTrace().getBaseLanguage();
Register register = lang.getRegister(space, offset, size);
if (register == null) {
// TODO: Is this too restrictive?
throw new IllegalArgumentException(
"read from register space must be from one register");
}
Register baseRegister = register.getBaseRegister();
CompletableFuture<Map<Register, RegisterValue>> future =
recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(),
Set.of(baseRegister));
return future.thenApply(map -> {
RegisterValue baseVal = map.get(baseRegister);
if (baseVal == null) {
return state.getVar(space, offset, size, quantize);
}
BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue();
return Utils.bigIntegerToBytes(val, size,
recorder.getTrace().getBaseLanguage().isBigEndian());
});
}
protected boolean isTargetSpace(AddressSpace space) {
return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() &&
!space.isUniqueSpace();
}
@Override
protected CompletableFuture<?> doSetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean quantize,
CompletableFuture<byte[]> val) {
if (!isTargetSpace(space)) {
return super.doSetVar(space, offset, size, quantize, val);
}
return offset.thenCompose(off -> val.thenCompose(v -> {
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.STORE);
return doSetTargetVar(space, lOff, size, quantize, v);
}));
}
@Override
protected CompletableFuture<byte[]> doGetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean quantize) {
if (!isTargetSpace(space)) {
return super.doGetVar(space, offset, size, quantize);
}
return offset.thenCompose(off -> {
TraceMemoryState ms = traceMemState.getVar(space, off, size, quantize);
if (ms == TraceMemoryState.KNOWN) {
return super.doGetVar(space, offset, size, quantize);
}
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.LOAD);
return doGetTargetVar(space, lOff, size, quantize);
});
}
}

View file

@ -0,0 +1,100 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec.debug.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
/**
* The most capable auxiliary emulator parts factory
*
* <p>
* This can manufacture parts for an emulator that is fully integrated with the Debugger UI, as well
* as all the parts for the less integrated forms of the same emulator. The pattern of use is
* generally to implement {@link DebuggerPcodeEmulatorFactory}, allowing the UI to discover and
* instantiate the emulator, though they could also be created directly by scripts or plugins.
*
* <p>
* For an example of a fully-integrated solution using this interface, see the Taint Analyzer. Its
* project serves as an archetype for similar dynamic analysis employing p-code emulation.
*
* <p>
* We recommend implementors start with the methods declared in {@link AuxEmulatorPartsFactory} with
* the aim of creating a derivative of {@link AuxPcodeEmulator}. Note that one Debugger-integrated
* emulator parts factory can be used with all three of {@link AuxPcodeEmulator},
* {@link AuxTracePcodeEmulator}, {@link AuxTraceEmulatorPartsFactory}. Once the stand-alone
* emulator has been tested, proceed to the methods in {@link AuxTraceEmulatorPartsFactory} with the
* aim of creating a derivative of {@link AuxTracePcodeEmulator}. Most of the work here is in
* factoring the state objects and pieces to reduce code duplication among the stand-alone and
* trace-integrated states. Once the trace-integrated emulator is tested, then proceed to the
* methods declared here in {@link AuxDebuggerEmulatorPartsFactory} with the aim of creating a
* derivative of {@link AuxDebuggerPcodeEmulator}. Again, most of the work is in factoring the
* states to avoid code duplication. Once the Debugger-integrated emulator is tested, the final bit
* is to implement a {@link DebuggerPcodeEmulatorFactory} so that users can configure and create the
* emulator. Other UI pieces, e.g., actions, fields, and table columns, may be needed to facilitate
* user access to the emulator's auxiliary state. Furthermore, a userop library for accessing the
* auxiliary state is recommended, since Sleigh code can be executed by the user.
*
* @param <U> the type of auxiliary values
*/
public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPartsFactory<U> {
/**
* Create the shared (memory) state of a new Debugger-integrated emulator
*
* <p>
* This state is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece},
* but it does not have to be. It must incorporate the concrete piece provided. The state must
* be capable of lazily loading state from a trace, from a live target, and from mapped static
* programs. It must also be able to write its cache into the trace at another snapshot. The
* given concrete piece is already capable of doing that for concrete values. The auxiliary
* piece can, at its discretion, delegate to the concrete piece in order to derive its values.
* It should be able to independently load its state from the trace and mapped static program,
* since this is one way a user expects to initialize the auxiliary values.
*
* @param emulator the emulator
* @param concrete the concrete piece
* @return the composed state
*/
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerSharedState(
AuxDebuggerPcodeEmulator<U> emulator,
ReadsTargetMemoryPcodeExecutorStatePiece concrete);
/**
* Create the local (register) state of a new Debugger-integrated thread
*
* <p>
* Like
* {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, ReadsTargetMemoryPcodeExecutorStatePiece)}
* this state must also be capable of lazily loading state from a trace and from a live target.
* Static programs can't be mapped into register space, so they do not apply here.
*
* @param emulator the emulator
* @param thread the new thread
* @param concrete the concrete piece
* @return the composed state
*/
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerLocalState(
AuxDebuggerPcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread,
ReadsTargetRegistersPcodeExecutorStatePiece concrete);
}

View file

@ -0,0 +1,91 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec.debug.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
import ghidra.trace.model.Trace;
/**
* An Debugger-integrated emulator whose parts are manufactured by a
* {@link AuxDebuggerEmulatorPartsFactory}
*
* <p>
* See the parts factory interface and its super interfaces:
* <ul>
* <li>{@link AuxDebuggerEmulatorPartsFactory}</li>
* <li>{@link AuxTraceEmulatorPartsFactory}</li>
* <li>{@link AuxEmulatorPartsFactory}</li>
* </ul>
*
* @param <U> the type of auxiliary values
*/
public abstract class AuxDebuggerPcodeEmulator<U> extends AuxTracePcodeEmulator<U>
implements DebuggerPcodeMachine<Pair<byte[], U>> {
protected final PluginTool tool;
protected final TraceRecorder recorder;
/**
* Create a new emulator
*
* @param tool the user's tool where the emulator is integrated
* @param trace the user's current trace from which the emulator loads state
* @param snap the user's current snapshot from which the emulator loads state
* @param recorder if applicable, the trace's recorder for its live target
*/
public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
super(trace, snap);
this.tool = tool;
this.recorder = recorder;
}
@Override
protected abstract AuxDebuggerEmulatorPartsFactory<U> getPartsFactory();
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createDebuggerSharedState(this,
new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder));
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createDebuggerLocalState(this, thread,
new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap,
getTraceThread(thread), 0,
recorder));
}
}

View file

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

View file

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

View file

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

View file

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