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