GP-2765: Re-factor RegionsProvider for object-based trace

This commit is contained in:
Dan 2022-11-04 16:17:54 -04:00
parent da766b7e69
commit 3327cc6bb8
25 changed files with 1962 additions and 794 deletions

View file

@ -0,0 +1,342 @@
/* ###
* 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.memory;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.util.Collection;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.ActionContext;
import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.DebuggerListingService;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.database.ObjectKey;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerLegacyRegionsPanel extends JPanel {
protected enum RegionTableColumns
implements EnumeratedTableColumn<RegionTableColumns, RegionRow> {
NAME("Name", String.class, RegionRow::getName, RegionRow::setName),
LIFESPAN("Lifespan", Lifespan.class, RegionRow::getLifespan),
START("Start", Address.class, RegionRow::getMinAddress),
END("End", Address.class, RegionRow::getMaxAddress),
LENGTH("Length", Long.class, RegionRow::getLength),
READ("Read", Boolean.class, RegionRow::isRead, RegionRow::setRead),
WRITE("Write", Boolean.class, RegionRow::isWrite, RegionRow::setWrite),
EXECUTE("Execute", Boolean.class, RegionRow::isExecute, RegionRow::setExecute),
VOLATILE("Volatile", Boolean.class, RegionRow::isVolatile, RegionRow::setVolatile);
private final String header;
private final Function<RegionRow, ?> getter;
private final BiConsumer<RegionRow, Object> setter;
private final Class<?> cls;
@SuppressWarnings("unchecked")
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter,
BiConsumer<RegionRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<RegionRow, Object>) setter;
}
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter) {
this(header, cls, getter, null);
}
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public boolean isEditable(RegionRow row) {
return setter != null;
}
@Override
public void setValueOf(RegionRow row, Object value) {
setter.accept(row, value);
}
@Override
public Object getValueOf(RegionRow row) {
return getter.apply(row);
}
}
protected static class RegionTableModel
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
public RegionTableModel(PluginTool tool) {
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new);
}
}
protected static RegionRow getSelectedRegionRow(ActionContext context) {
if (!(context instanceof DebuggerRegionActionContext)) {
return null;
}
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
Set<RegionRow> regions = ctx.getSelectedRegions();
if (regions.size() != 1) {
return null;
}
return regions.iterator().next();
}
protected static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
if (!(context instanceof DebuggerRegionActionContext)) {
return null;
}
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
return ctx.getSelectedRegions()
.stream()
.map(r -> r.getRegion())
.collect(Collectors.toSet());
}
private class RegionsListener extends TraceDomainObjectListener {
public RegionsListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionAdded);
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionDeleted);
}
private void objectRestored() {
loadRegions();
}
private void regionAdded(TraceMemoryRegion region) {
regionTableModel.addItem(region);
}
private void regionChanged(TraceMemoryRegion region) {
regionTableModel.updateItem(region);
}
private void regionDeleted(TraceMemoryRegion region) {
regionTableModel.deleteItem(region);
}
}
protected void activatedSelectAddresses(DebuggerRegionActionContext ctx) {
if (listingService == null) {
return;
}
Set<TraceMemoryRegion> regions = getSelectedRegions(ctx);
if (regions == null) {
return;
}
AddressSet sel = new AddressSet();
for (TraceMemoryRegion s : regions) {
sel.add(s.getRange());
}
ProgramSelection ps = new ProgramSelection(sel);
listingService.setCurrentSelection(ps);
}
final DebuggerRegionsProvider provider;
@AutoServiceConsumed
private DebuggerListingService listingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
private Trace currentTrace;
private final RegionsListener regionsListener = new RegionsListener();
protected final RegionTableModel regionTableModel;
protected GhidraTable regionTable;
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
private DebuggerRegionActionContext myActionContext;
public DebuggerLegacyRegionsPanel(DebuggerRegionsProvider provider) {
super(new BorderLayout());
this.provider = provider;
this.autoServiceWiring = AutoService.wireServicesConsumed(provider.plugin, this);
regionTableModel = new RegionTableModel(provider.getTool());
regionTable = new GhidraTable(regionTableModel);
regionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
add(new JScrollPane(regionTable));
regionFilterPanel = new GhidraTableFilterPanel<>(regionTable, regionTableModel);
add(regionFilterPanel, BorderLayout.SOUTH);
regionTable.getSelectionModel().addListSelectionListener(evt -> {
myActionContext = new DebuggerRegionActionContext(provider,
regionFilterPanel.getSelectedItems(), regionTable);
contextChanged();
});
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
regionTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
navigateToSelectedRegion();
}
}
});
regionTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
navigateToSelectedRegion();
}
}
});
// TODO: Adjust default column widths?
TableColumnModel columnModel = regionTable.getColumnModel();
TableColumn startCol = columnModel.getColumn(RegionTableColumns.START.ordinal());
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn endCol = columnModel.getColumn(RegionTableColumns.END.ordinal());
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lenCol = columnModel.getColumn(RegionTableColumns.LENGTH.ordinal());
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
final int small = 100;
TableColumn rCol = columnModel.getColumn(RegionTableColumns.READ.ordinal());
rCol.setPreferredWidth(small);
TableColumn wCol = columnModel.getColumn(RegionTableColumns.WRITE.ordinal());
wCol.setPreferredWidth(small);
TableColumn eCol = columnModel.getColumn(RegionTableColumns.EXECUTE.ordinal());
eCol.setPreferredWidth(small);
TableColumn vCol = columnModel.getColumn(RegionTableColumns.VOLATILE.ordinal());
vCol.setPreferredWidth(small);
}
private void loadRegions() {
regionTableModel.clear();
if (currentTrace == null) {
return;
}
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
regionTableModel.addAllItems(memoryManager.getAllRegions());
}
public DebuggerRegionActionContext getActionContext() {
return myActionContext;
}
boolean isContextNonEmpty(DebuggerRegionActionContext ctx) {
return !ctx.getSelectedRegions().isEmpty();
}
private static Set<TraceMemoryRegion> getSelectedRegions(DebuggerRegionActionContext ctx) {
if (ctx == null) {
return null;
}
return ctx.getSelectedRegions()
.stream()
.map(r -> r.getRegion())
.collect(Collectors.toSet());
}
protected void navigateToSelectedRegion() {
if (listingService != null) {
int selectedRow = regionTable.getSelectedRow();
int selectedColumn = regionTable.getSelectedColumn();
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
listingService.goTo((Address) value, true);
}
}
}
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable,
regionTableModel, regionFilterPanel);
}
public Collection<RegionRow> getSelectedRows() {
return regionFilterPanel.getSelectedItems();
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
setTrace(coordinates.getTrace());
}
public void setTrace(Trace trace) {
if (currentTrace == trace) {
return;
}
removeOldListeners();
currentTrace = trace;
addNewListeners();
loadRegions();
contextChanged();
}
public void contextChanged() {
provider.contextChanged();
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(regionsListener);
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(regionsListener);
}
}

View file

@ -0,0 +1,375 @@
/* ###
* 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.memory;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.model.*;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
import ghidra.app.plugin.core.debug.gui.model.columns.*;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.HTMLUtilities;
public class DebuggerRegionsPanel extends ObjectsTablePanel
implements ListSelectionListener, CellActivationListener {
private static class RegionKeyColumn extends TraceValueKeyColumn {
@Override
public String getColumnName() {
return "Key";
}
}
private static class RegionPathColumn extends TraceValueKeyColumn {
@Override
public String getColumnName() {
return "Path";
}
@Override
public String getValue(ValueRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObject.getValue().getCanonicalPath().toString();
}
}
private static class RegionNameColumn extends TraceValueValColumn {
@Override
public String getColumnName() {
return "Name";
}
}
private abstract static class ValueAddress extends ValueDerivedProperty<Address> {
public ValueAddress(ValueRow row) {
super(row, Address.class);
}
@Override
public String getDisplay() {
Address value = getValue();
return value == null ? "" : value.toString();
}
@Override
public String getHtmlDisplay() {
Address value = getValue();
return value == null ? ""
: ("<html><body style='font-family:monospaced'>" +
HTMLUtilities.escapeHTML(value.toString()));
}
@Override
public String getToolTip() {
Address value = getValue();
return value == null ? "" : value.toString(true);
}
@Override
public boolean isModified() {
return false;
}
}
private abstract static class RegionAddressColumn
extends TraceValueObjectPropertyColumn<Address> {
public RegionAddressColumn() {
super(Address.class);
}
abstract Address fromRange(AddressRange range);
@Override
public ValueProperty<Address> getProperty(ValueRow row) {
return new ValueAddress(row) {
@Override
public Address getValue() {
TraceObjectValue entry =
row.getAttributeEntry(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
return entry == null || !(entry.getValue() instanceof AddressRange range)
? null
: fromRange(range);
}
};
}
}
private static class RegionStartColumn extends RegionAddressColumn {
@Override
public String getColumnName() {
return "Start";
}
@Override
Address fromRange(AddressRange range) {
return range.getMinAddress();
}
}
private static class RegionEndColumn extends RegionAddressColumn {
@Override
public String getColumnName() {
return "End";
}
@Override
Address fromRange(AddressRange range) {
return range.getMaxAddress();
}
}
private static class RegionLengthColumn extends TraceValueObjectPropertyColumn<Long> {
public RegionLengthColumn() {
super(Long.class);
}
@Override
public String getColumnName() {
return "Length";
}
@Override
public ValueProperty<Long> getProperty(ValueRow row) {
return new ValueDerivedProperty<>(row, Long.class) {
@Override
public Long getValue() {
TraceObjectValue entry =
row.getAttributeEntry(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
return entry == null || !(entry.getValue() instanceof AddressRange range)
? null
: range.getLength();
}
@Override
public String getDisplay() {
Long value = getValue();
return value == null ? "" : ("0x" + Long.toUnsignedString(value, 16));
}
@Override
public String getHtmlDisplay() {
Long value = getValue();
return value == null ? ""
: ("<html><body style='font-family:monospaced'>0x" +
Long.toUnsignedString(value, 16));
}
@Override
public String getToolTip() {
return getDisplay();
}
@Override
public boolean isModified() {
return false;
}
};
}
}
public abstract static class RegionFlagColumn extends TraceValueObjectAttributeColumn<Boolean> {
public RegionFlagColumn(String attributeName) {
super(attributeName, Boolean.class);
}
@Override
public int getColumnPreferredWidth() {
return 80;
}
}
public static class RegionReadColumn extends RegionFlagColumn {
public RegionReadColumn() {
super(TargetMemoryRegion.READABLE_ATTRIBUTE_NAME);
}
@Override
public String getColumnName() {
return "Read";
}
}
public static class RegionWriteColumn extends RegionFlagColumn {
public RegionWriteColumn() {
super(TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME);
}
@Override
public String getColumnName() {
return "Write";
}
}
public static class RegionExecuteColumn extends RegionFlagColumn {
public RegionExecuteColumn() {
super(TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME);
}
@Override
public String getColumnName() {
return "Execute";
}
}
private class RegionTableModel extends ObjectTableModel {
protected RegionTableModel(Plugin plugin) {
super(plugin);
}
@Override
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
descriptor.addHiddenColumn(new RegionKeyColumn());
descriptor.addHiddenColumn(new RegionPathColumn());
descriptor.addVisibleColumn(new RegionNameColumn());
descriptor.addVisibleColumn(new RegionStartColumn());
descriptor.addVisibleColumn(new RegionEndColumn());
descriptor.addVisibleColumn(new RegionLengthColumn());
descriptor.addVisibleColumn(new RegionReadColumn());
descriptor.addVisibleColumn(new RegionWriteColumn());
descriptor.addVisibleColumn(new RegionExecuteColumn());
return descriptor;
}
}
private final DebuggerRegionsProvider provider;
private DebuggerObjectActionContext myActionContext;
public DebuggerRegionsPanel(DebuggerRegionsProvider provider) {
super(provider.plugin);
this.provider = provider;
setLimitToSnap(true);
setShowHidden(false);
addSelectionListener(this);
addCellActivationListener(this);
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
return new RegionTableModel(plugin);
}
public DebuggerObjectActionContext getActionContext() {
return myActionContext;
}
protected static ModelQuery successorRegions(TargetObjectSchema rootSchema, List<String> path) {
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
return new ModelQuery(schema.searchFor(TargetMemoryRegion.class, path, true));
}
protected ModelQuery computeQuery(TraceObject object) {
if (object == null) {
return ModelQuery.EMPTY;
}
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
List<String> seedPath = object.getCanonicalPath().getKeyList();
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
if (processPath != null) {
return successorRegions(rootSchema, processPath);
}
List<String> memoryPath = rootSchema.searchForSuitable(TargetMemory.class, seedPath);
if (memoryPath != null) {
return successorRegions(rootSchema, memoryPath);
}
return successorRegions(rootSchema, List.of());
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
TraceObject object = coordinates.getObject();
setQuery(computeQuery(object));
goToCoordinates(coordinates);
}
boolean isContextNonEmpty(DebuggerObjectActionContext ctx) {
return ctx != null && !ctx.getObjectValues().isEmpty();
}
protected static Set<TraceMemoryRegion> getSelectedRegions(DebuggerObjectActionContext ctx) {
return ctx == null ? null
: ctx.getObjectValues()
.stream()
.filter(v -> v.isObject())
.map(v -> v.getChild().queryInterface(TraceObjectMemoryRegion.class))
.filter(r -> r != null)
.collect(Collectors.toSet());
}
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
trySelect(sel.stream()
.filter(r -> r instanceof TraceObjectMemoryRegion)
.map(r -> ((TraceObjectMemoryRegion) r).getObject())
.collect(Collectors.toSet()));
}
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
List<ValueRow> sel = getSelectedItems();
if (!sel.isEmpty()) {
myActionContext = new DebuggerObjectActionContext(
sel.stream().map(r -> r.getValue()).collect(Collectors.toList()), provider, this);
}
}
@Override
public void cellActivated(JTable table) {
if (provider.listingService == null) {
return;
}
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object value = table.getValueAt(row, col);
if (!(value instanceof ValueProperty<?> property)) {
return;
}
Object propVal = property.getValue();
if (propVal instanceof Address address) {
provider.listingService.goTo(address, true);
}
else if (propVal instanceof AddressRange range) {
provider.listingService.setCurrentSelection(
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
provider.listingService.goTo(range.getMinAddress(), true);
}
}
}

View file

@ -78,7 +78,7 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
} }
else if (event instanceof TraceActivatedPluginEvent) { else if (event instanceof TraceActivatedPluginEvent) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.setTrace(ev.getActiveCoordinates().getTrace()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
} }

View file

@ -16,52 +16,58 @@
package ghidra.app.plugin.core.debug.gui.memory; package ghidra.app.plugin.core.debug.gui.memory;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.*; import java.awt.event.MouseEvent;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel; import org.apache.commons.lang3.ArrayUtils;
import docking.ActionContext; import docking.ActionContext;
import docking.WindowPosition; import docking.WindowPosition;
import docking.action.*; import docking.action.*;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.widgets.table.CustomToStringCellRenderer; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog; import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider;
import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand; import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.*; import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.database.ObjectKey;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerRegionsProvider extends ComponentProviderAdapter { public class DebuggerRegionsProvider extends ComponentProviderAdapter {
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
}
if (a.getSnap() != b.getSnap()) {
return false;
}
if (!Objects.equals(a.getObject(), b.getObject())) {
return false;
}
return true;
}
interface MapRegionsAction { interface MapRegionsAction {
String NAME = DebuggerResources.NAME_MAP_REGIONS; String NAME = DebuggerResources.NAME_MAP_REGIONS;
String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_REGIONS; String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_REGIONS;
@ -108,122 +114,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
} }
} }
protected enum RegionTableColumns
implements EnumeratedTableColumn<RegionTableColumns, RegionRow> {
NAME("Name", String.class, RegionRow::getName, RegionRow::setName),
LIFESPAN("Lifespan", Lifespan.class, RegionRow::getLifespan),
START("Start", Address.class, RegionRow::getMinAddress),
END("End", Address.class, RegionRow::getMaxAddress),
LENGTH("Length", Long.class, RegionRow::getLength),
READ("Read", Boolean.class, RegionRow::isRead, RegionRow::setRead),
WRITE("Write", Boolean.class, RegionRow::isWrite, RegionRow::setWrite),
EXECUTE("Execute", Boolean.class, RegionRow::isExecute, RegionRow::setExecute),
VOLATILE("Volatile", Boolean.class, RegionRow::isVolatile, RegionRow::setVolatile);
private final String header;
private final Function<RegionRow, ?> getter;
private final BiConsumer<RegionRow, Object> setter;
private final Class<?> cls;
@SuppressWarnings("unchecked")
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter,
BiConsumer<RegionRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<RegionRow, Object>) setter;
}
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter) {
this(header, cls, getter, null);
}
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public boolean isEditable(RegionRow row) {
return setter != null;
}
@Override
public void setValueOf(RegionRow row, Object value) {
setter.accept(row, value);
}
@Override
public Object getValueOf(RegionRow row) {
return getter.apply(row);
}
}
protected static class RegionTableModel
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
public RegionTableModel(PluginTool tool) {
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new);
}
}
protected static RegionRow getSelectedRegionRow(ActionContext context) {
if (!(context instanceof DebuggerRegionActionContext)) {
return null;
}
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
Set<RegionRow> regions = ctx.getSelectedRegions();
if (regions.size() != 1) {
return null;
}
return regions.iterator().next();
}
protected static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
if (!(context instanceof DebuggerRegionActionContext)) {
return null;
}
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
return ctx.getSelectedRegions()
.stream()
.map(r -> r.getRegion())
.collect(Collectors.toSet());
}
private class RegionsListener extends TraceDomainObjectListener {
public RegionsListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionAdded);
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionDeleted);
}
private void objectRestored() {
loadRegions();
}
private void regionAdded(TraceMemoryRegion region) {
regionTableModel.addItem(region);
}
private void regionChanged(TraceMemoryRegion region) {
regionTableModel.updateItem(region);
}
private void regionDeleted(TraceMemoryRegion region) {
regionTableModel.deleteItem(region);
}
}
protected class SelectAddressesAction extends AbstractSelectAddressesAction { protected class SelectAddressesAction extends AbstractSelectAddressesAction {
public static final String GROUP = DebuggerResources.GROUP_GENERAL; public static final String GROUP = DebuggerResources.GROUP_GENERAL;
@ -241,7 +131,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
if (listingService == null) { if (listingService == null) {
return; return;
} }
Set<TraceMemoryRegion> regions = getSelectedRegions(myActionContext); Set<TraceMemoryRegion> regions = getSelectedRegions(context);
if (regions == null) { if (regions == null) {
return; return;
} }
@ -255,42 +145,36 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext); Set<TraceMemoryRegion> sel = getSelectedRegions(context);
return sel != null && !sel.isEmpty(); return sel != null && !sel.isEmpty();
} }
} }
private final DebuggerRegionsPlugin plugin; final DebuggerRegionsPlugin plugin;
@AutoServiceConsumed
ProgramManager programManager;
@AutoServiceConsumed
DebuggerListingService listingService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerStaticMappingService staticMappingService; private DebuggerStaticMappingService staticMappingService;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
ProgramManager programManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
private Trace currentTrace; private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private Program currentProgram;
private final RegionsListener regionsListener = new RegionsListener(); private ProgramLocation currentLocation;
protected final RegionTableModel regionTableModel;
protected GhidraTable regionTable;
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
private final JPanel mainPanel = new JPanel(new BorderLayout()); private final JPanel mainPanel = new JPanel(new BorderLayout());
DebuggerRegionsPanel panel;
DebuggerLegacyRegionsPanel legacyPanel;
// TODO: Lazy construction of these dialogs? // TODO: Lazy construction of these dialogs?
private final DebuggerBlockChooserDialog blockChooserDialog; private final DebuggerBlockChooserDialog blockChooserDialog;
private final DebuggerRegionMapProposalDialog regionProposalDialog; private final DebuggerRegionMapProposalDialog regionProposalDialog;
private DebuggerRegionActionContext myActionContext;
private Program currentProgram;
private ProgramLocation currentLocation;
DockingAction actionMapRegions; DockingAction actionMapRegions;
DockingAction actionMapRegionTo; DockingAction actionMapRegionTo;
DockingAction actionMapRegionsTo; DockingAction actionMapRegionsTo;
@ -304,8 +188,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
DebuggerRegionActionContext.class); DebuggerRegionActionContext.class);
this.plugin = plugin; this.plugin = plugin;
regionTableModel = new RegionTableModel(tool);
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS); setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS); setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
setWindowMenuGroup(DebuggerPluginPackage.NAME); setWindowMenuGroup(DebuggerPluginPackage.NAME);
@ -322,103 +204,28 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
createActions(); createActions();
} }
@Override protected static boolean isLegacy(Trace trace) {
public ActionContext getActionContext(MouseEvent event) { return trace != null && trace.getObjectManager().getRootSchema() == null;
if (myActionContext == null) {
return super.getActionContext(event);
}
return myActionContext;
}
private void loadRegions() {
regionTableModel.clear();
if (currentTrace == null) {
return;
}
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
regionTableModel.addAllItems(memoryManager.getAllRegions());
} }
protected void buildMainPanel() { protected void buildMainPanel() {
regionTable = new GhidraTable(regionTableModel); panel = new DebuggerRegionsPanel(this);
regionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); mainPanel.add(panel);
mainPanel.add(new JScrollPane(regionTable)); legacyPanel = new DebuggerLegacyRegionsPanel(this);
regionFilterPanel = new GhidraTableFilterPanel<>(regionTable, regionTableModel);
mainPanel.add(regionFilterPanel, BorderLayout.SOUTH);
regionTable.getSelectionModel().addListSelectionListener(evt -> {
myActionContext = new DebuggerRegionActionContext(this,
regionFilterPanel.getSelectedItems(), regionTable);
contextChanged();
});
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
regionTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
navigateToSelectedRegion();
}
}
});
regionTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
navigateToSelectedRegion();
}
}
});
// TODO: Adjust default column widths?
TableColumnModel columnModel = regionTable.getColumnModel();
TableColumn startCol = columnModel.getColumn(RegionTableColumns.START.ordinal());
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn endCol = columnModel.getColumn(RegionTableColumns.END.ordinal());
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lenCol = columnModel.getColumn(RegionTableColumns.LENGTH.ordinal());
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
final int small = 100;
TableColumn rCol = columnModel.getColumn(RegionTableColumns.READ.ordinal());
rCol.setPreferredWidth(small);
TableColumn wCol = columnModel.getColumn(RegionTableColumns.WRITE.ordinal());
wCol.setPreferredWidth(small);
TableColumn eCol = columnModel.getColumn(RegionTableColumns.EXECUTE.ordinal());
eCol.setPreferredWidth(small);
TableColumn vCol = columnModel.getColumn(RegionTableColumns.VOLATILE.ordinal());
vCol.setPreferredWidth(small);
}
protected void navigateToSelectedRegion() {
if (listingService != null) {
int selectedRow = regionTable.getSelectedRow();
int selectedColumn = regionTable.getSelectedColumn();
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
listingService.goTo((Address) value, true);
}
}
} }
protected void createActions() { protected void createActions() {
actionMapRegions = MapRegionsAction.builder(plugin) actionMapRegions = MapRegionsAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class)
.enabledWhen(this::isContextNonEmpty) .enabledWhen(this::isContextNonEmpty)
.popupWhen(this::isContextNonEmpty) .popupWhen(this::isContextNonEmpty)
.onAction(this::activatedMapRegions) .onAction(this::activatedMapRegions)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapRegionTo = MapRegionToAction.builder(plugin) actionMapRegionTo = MapRegionToAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class) .enabledWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx))
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1) .popupWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx))
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1)
.onAction(this::activatedMapRegionTo) .onAction(this::activatedMapRegionTo)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionMapRegionsTo = MapRegionsToAction.builder(plugin) actionMapRegionsTo = MapRegionsToAction.builder(plugin)
.withContext(DebuggerRegionActionContext.class)
.enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx)) .enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
.popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx)) .popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
.onAction(this::activatedMapRegionsTo) .onAction(this::activatedMapRegionsTo)
@ -426,50 +233,76 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
actionSelectAddresses = new SelectAddressesAction(); actionSelectAddresses = new SelectAddressesAction();
actionSelectRows = SelectRowsAction.builder(plugin) actionSelectRows = SelectRowsAction.builder(plugin)
.description("Select regions by trace selection") .description("Select regions by trace selection")
.enabledWhen(ctx -> currentTrace != null) .enabledWhen(ctx -> current.getTrace() != null)
.onAction(this::activatedSelectCurrent) .onAction(this::activatedSelectCurrent)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionForceFullView = ForceFullViewAction.builder(plugin) actionForceFullView = ForceFullViewAction.builder(plugin)
.enabledWhen(ctx -> currentTrace != null) .enabledWhen(ctx -> current.getTrace() != null)
.onAction(this::activatedForceFullView) .onAction(this::activatedForceFullView)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
contextChanged(); contextChanged();
} }
private boolean isContextNonEmpty(DebuggerRegionActionContext ctx) { @Override
return !ctx.getSelectedRegions().isEmpty(); public ActionContext getActionContext(MouseEvent event) {
} final ActionContext context;
if (isLegacy(current.getTrace())) {
private static Set<TraceMemoryRegion> getSelectedRegions(DebuggerRegionActionContext ctx) { context = legacyPanel.getActionContext();
if (ctx == null) {
return null;
} }
return ctx.getSelectedRegions() else {
.stream() context = panel.getActionContext();
.map(r -> r.getRegion())
.collect(Collectors.toSet());
}
private void activatedMapRegions(DebuggerRegionActionContext ignored) {
mapRegions(getSelectedRegions(myActionContext));
}
private void activatedMapRegionsTo(DebuggerRegionActionContext ignored) {
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
if (sel == null || sel.isEmpty()) {
return;
} }
mapRegionsTo(sel); if (context != null) {
return context;
}
return super.getActionContext(event);
} }
private void activatedMapRegionTo(DebuggerRegionActionContext ignored) { private boolean isContextNonEmpty(ActionContext context) {
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext); if (context instanceof DebuggerRegionActionContext legacyCtx) {
return legacyPanel.isContextNonEmpty(legacyCtx);
}
else if (context instanceof DebuggerObjectActionContext ctx) {
return panel.isContextNonEmpty(ctx);
}
return false;
}
private boolean isContextSingleSelection(ActionContext context) {
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
return sel != null && sel.size() == 1;
}
private static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
if (context instanceof DebuggerRegionActionContext legacyCtx) {
return DebuggerLegacyRegionsPanel.getSelectedRegions(legacyCtx);
}
else if (context instanceof DebuggerObjectActionContext ctx) {
return DebuggerRegionsPanel.getSelectedRegions(ctx);
}
return null;
}
private void activatedMapRegions(ActionContext context) {
mapRegions(getSelectedRegions(context));
}
private void activatedMapRegionTo(ActionContext context) {
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
if (sel == null || sel.size() != 1) { if (sel == null || sel.size() != 1) {
return; return;
} }
mapRegionTo(sel.iterator().next()); mapRegionTo(sel.iterator().next());
} }
private void activatedMapRegionsTo(ActionContext context) {
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
if (sel == null || sel.isEmpty()) {
return;
}
mapRegionsTo(sel);
}
protected void promptRegionProposal(Collection<RegionMapEntry> proposal) { protected void promptRegionProposal(Collection<RegionMapEntry> proposal) {
if (proposal.isEmpty()) { if (proposal.isEmpty()) {
Msg.showInfo(this, getComponent(), "Map Regions", Msg.showInfo(this, getComponent(), "Map Regions",
@ -483,7 +316,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
return; return;
} }
tool.executeBackgroundCommand( tool.executeBackgroundCommand(
new MapRegionsBackgroundCommand(staticMappingService, adjusted), currentTrace); new MapRegionsBackgroundCommand(staticMappingService, adjusted), current.getTrace());
} }
protected void mapRegions(Set<TraceMemoryRegion> regions) { protected void mapRegions(Set<TraceMemoryRegion> regions) {
@ -524,13 +357,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
} }
private void activatedSelectCurrent(ActionContext ignored) { private void activatedSelectCurrent(ActionContext ignored) {
if (listingService == null || traceManager == null || currentTrace == null) { if (listingService == null || traceManager == null || current.getTrace() == null) {
return; return;
} }
// TODO: Select from other listings? // TODO: Select from other listings?
ProgramSelection progSel = listingService.getCurrentSelection(); ProgramSelection progSel = listingService.getCurrentSelection();
TraceMemoryManager memoryManager = currentTrace.getMemoryManager(); TraceMemoryManager memoryManager = current.getTrace().getMemoryManager();
if (progSel != null && !progSel.isEmpty()) { if (progSel != null && !progSel.isEmpty()) {
Set<TraceMemoryRegion> regSel = new HashSet<>(); Set<TraceMemoryRegion> regSel = new HashSet<>();
for (AddressRange range : progSel) { for (AddressRange range : progSel) {
@ -552,21 +385,22 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
} }
private void activatedForceFullView(ActionContext ignored) { private void activatedForceFullView(ActionContext ignored) {
if (currentTrace == null) { if (current.getTrace() == null) {
return; return;
} }
currentTrace.getProgramView() current.getTrace()
.getProgramView()
.getMemory() .getMemory()
.setForceFullView(actionForceFullView.isSelected()); .setForceFullView(actionForceFullView.isSelected());
} }
public void setSelectedRegions(Set<TraceMemoryRegion> sel) { public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable, if (isLegacy(current.getTrace())) {
regionTableModel, regionFilterPanel); legacyPanel.setSelectedRegions(sel);
} }
else {
public Collection<RegionRow> getSelectedRows() { panel.setSelectedRegions(sel);
return regionFilterPanel.getSelectedItems(); }
} }
@Override @Override
@ -602,41 +436,17 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
} }
} }
public void setTrace(Trace trace) {
if (currentTrace == trace) {
return;
}
removeOldListeners();
currentTrace = trace;
addNewListeners();
loadRegions();
contextChanged();
}
@Override @Override
public void contextChanged() { public void contextChanged() {
super.contextChanged(); super.contextChanged();
if (currentTrace != null) { if (current.getTrace() != null) {
actionForceFullView.setSelected(currentTrace.getProgramView() actionForceFullView.setSelected(current.getTrace()
.getProgramView()
.getMemory() .getMemory()
.isForceFullView()); .isForceFullView());
} }
} }
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(regionsListener);
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(regionsListener);
}
public Entry<Program, MemoryBlock> askBlock(TraceMemoryRegion region, Program program, public Entry<Program, MemoryBlock> askBlock(TraceMemoryRegion region, Program program,
MemoryBlock block) { MemoryBlock block) {
if (programManager == null) { if (programManager == null) {
@ -646,4 +456,32 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
return blockChooserDialog.chooseBlock(getTool(), region, return blockChooserDialog.chooseBlock(getTool(), region,
List.of(programManager.getAllOpenPrograms())); List.of(programManager.getAllOpenPrograms()));
} }
public void coordinatesActivated(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
current = coordinates;
if (isLegacy(coordinates.getTrace())) {
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
legacyPanel.coordinatesActivated(coordinates);
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {
mainPanel.remove(panel);
mainPanel.add(legacyPanel);
mainPanel.validate();
}
}
else {
legacyPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
panel.coordinatesActivated(coordinates);
if (ArrayUtils.indexOf(mainPanel.getComponents(), panel) == -1) {
mainPanel.remove(legacyPanel);
mainPanel.add(panel);
mainPanel.validate();
}
}
}
} }

View file

@ -21,6 +21,7 @@ import java.util.stream.Stream;
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener; import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.Trace.TraceObjectChangeType;
@ -36,6 +37,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
protected class ListenerForChanges extends TraceDomainObjectListener { protected class ListenerForChanges extends TraceDomainObjectListener {
public ListenerForChanges() { public ListenerForChanges() {
listenForUntyped(Trace.DO_OBJECT_RESTORED, this::objectRestored);
listenFor(TraceObjectChangeType.VALUE_CREATED, this::valueCreated); listenFor(TraceObjectChangeType.VALUE_CREATED, this::valueCreated);
listenFor(TraceObjectChangeType.VALUE_DELETED, this::valueDeleted); listenFor(TraceObjectChangeType.VALUE_DELETED, this::valueDeleted);
listenFor(TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, this::valueLifespanChanged); listenFor(TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, this::valueLifespanChanged);
@ -44,6 +46,10 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
listenFor(TraceSnapshotChangeType.DELETED, this::maxSnapChanged); listenFor(TraceSnapshotChangeType.DELETED, this::maxSnapChanged);
} }
protected void objectRestored(DomainObjectChangeRecord record) {
reload();
}
protected void valueCreated(TraceObjectValue value) { protected void valueCreated(TraceObjectValue value) {
if (query != null && query.involves(span, value)) { if (query != null && query.involves(span, value)) {
reload(); // Can I be more surgical? reload(); // Can I be more surgical?

View file

@ -17,12 +17,11 @@ package ghidra.app.plugin.core.debug.gui.model;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.event.KeyListener; import java.awt.event.*;
import java.awt.event.MouseListener; import java.util.*;
import java.util.List; import java.util.stream.Collectors;
import javax.swing.JPanel; import javax.swing.*;
import javax.swing.JScrollPane;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import docking.widgets.table.DynamicTableColumn; import docking.widgets.table.DynamicTableColumn;
@ -32,12 +31,17 @@ import ghidra.framework.plugintool.Plugin;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableModel<T>> public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableModel<T>>
extends JPanel { extends JPanel {
public interface CellActivationListener {
void cellActivated(JTable table);
}
protected final M tableModel; protected final M tableModel;
protected final GhidraTable table; protected final GhidraTable table;
protected final GhidraTableFilterPanel<T> filterPanel; protected final GhidraTableFilterPanel<T> filterPanel;
@ -46,6 +50,9 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
protected boolean limitToSnap = false; protected boolean limitToSnap = false;
protected boolean showHidden = false; protected boolean showHidden = false;
private final ListenerSet<CellActivationListener> cellActivationListeners =
new ListenerSet<>(CellActivationListener.class);
public AbstractQueryTablePanel(Plugin plugin) { public AbstractQueryTablePanel(Plugin plugin) {
super(new BorderLayout()); super(new BorderLayout());
tableModel = createModel(plugin); tableModel = createModel(plugin);
@ -54,6 +61,24 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
add(new JScrollPane(table), BorderLayout.CENTER); add(new JScrollPane(table), BorderLayout.CENTER);
add(filterPanel, BorderLayout.SOUTH); add(filterPanel, BorderLayout.SOUTH);
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
fireCellActivated();
}
}
});
table.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
fireCellActivated();
e.consume();
}
}
});
} }
protected abstract M createModel(Plugin plugin); protected abstract M createModel(Plugin plugin);
@ -121,32 +146,12 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
table.getSelectionModel().removeListSelectionListener(listener); table.getSelectionModel().removeListSelectionListener(listener);
} }
@Override public void addCellActivationListener(CellActivationListener listener) {
public synchronized void addMouseListener(MouseListener l) { cellActivationListeners.add(listener);
super.addMouseListener(l);
// HACK?
table.addMouseListener(l);
} }
@Override public void removeCellActivationListener(CellActivationListener listener) {
public synchronized void removeMouseListener(MouseListener l) { cellActivationListeners.remove(listener);
super.removeMouseListener(l);
// HACK?
table.removeMouseListener(l);
}
@Override
public synchronized void addKeyListener(KeyListener l) {
super.addKeyListener(l);
// HACK?
table.addKeyListener(l);
}
@Override
public synchronized void removeKeyListener(KeyListener l) {
super.removeKeyListener(l);
// HACK?
table.removeKeyListener(l);
} }
public void addSeekListener(SeekListener listener) { public void addSeekListener(SeekListener listener) {
@ -161,15 +166,20 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
return table.getSelectionModel().getSelectionMode(); return table.getSelectionModel().getSelectionMode();
} }
// TODO: setSelectedItems? Is a bit more work than expected:
// see filterPanel.getTableFilterModel();
// see table.getSelectionMode().addSelectionInterval()
// seems like setSelectedItems should be in filterPanel?
public void setSelectedItem(T item) { public void setSelectedItem(T item) {
filterPanel.setSelectedItem(item); filterPanel.setSelectedItem(item);
} }
public void setSelectedItems(Collection<T> items) {
table.clearSelection();
for (T t : items) {
int modelRow = tableModel.getRowIndex(t);
int viewRow = filterPanel.getViewRow(modelRow);
table.getSelectionModel().addSelectionInterval(viewRow, viewRow);
}
table.scrollToSelectedRow();
}
public boolean trySelect(TraceObject object) { public boolean trySelect(TraceObject object) {
T t = tableModel.findTraceObject(object); T t = tableModel.findTraceObject(object);
if (t == null) { if (t == null) {
@ -179,6 +189,15 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
return true; return true;
} }
public boolean trySelect(Collection<TraceObject> objects) {
List<T> ts = objects.stream().map(tableModel::findTraceObject).collect(Collectors.toList());
if (ts.isEmpty()) {
return false;
}
setSelectedItems(ts);
return true;
}
public List<T> getSelectedItems() { public List<T> getSelectedItems() {
return filterPanel.getSelectedItems(); return filterPanel.getSelectedItems();
} }
@ -192,7 +211,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <V> DynamicTableColumn<T, V, Trace> getColumnByNameAndType(String name, Class<V> type) { public <V> Map.Entry<Integer, DynamicTableColumn<T, V, Trace>> getColumnByNameAndType(
String name, Class<V> type) {
int count = tableModel.getColumnCount(); int count = tableModel.getColumnCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
DynamicTableColumn<T, ?, ?> column = tableModel.getColumn(i); DynamicTableColumn<T, ?, ?> column = tableModel.getColumn(i);
@ -202,7 +222,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
if (column.getColumnClass() != type) { if (column.getColumnClass() != type) {
continue; continue;
} }
return (DynamicTableColumn<T, V, Trace>) column; return Map.entry(table.convertColumnIndexToView(i),
(DynamicTableColumn<T, V, Trace>) column);
} }
return null; return null;
} }
@ -214,4 +235,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
public void setDiffColorSel(Color diffColorSel) { public void setDiffColorSel(Color diffColorSel) {
tableModel.setDiffColorSel(diffColorSel); tableModel.setDiffColorSel(diffColorSel);
} }
protected void fireCellActivated() {
cellActivationListeners.fire.cellActivated(table);
}
} }

View file

@ -34,6 +34,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider; import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode; import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
@ -106,13 +107,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
ToggleDockingAction actionShowPrimitivesInTree; ToggleDockingAction actionShowPrimitivesInTree;
ToggleDockingAction actionShowMethodsInTree; ToggleDockingAction actionShowMethodsInTree;
DockingAction actionFollowLink; DockingAction actionFollowLink;
// TODO: Remove stopgap
DockingAction actionStepBackward;
DockingAction actionStepForward;
DebuggerObjectActionContext myActionContext; DebuggerObjectActionContext myActionContext;
private SeekListener seekListener = pos -> { private final CellActivationListener elementActivationListener =
table -> activatedElementsTable();
private final CellActivationListener attributeActivationListener =
table -> activatedAttributesTable();
private final SeekListener seekListener = pos -> {
long snap = Math.round(pos); long snap = Math.round(pos);
if (current.getTrace() == null || snap < 0) { if (current.getTrace() == null || snap < 0) {
snap = 0; snap = 0;
@ -320,44 +323,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
} }
}); });
elementsTablePanel.addMouseListener(new MouseAdapter() { elementsTablePanel.addCellActivationListener(elementActivationListener);
@Override attributesTablePanel.addCellActivationListener(attributeActivationListener);
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) {
return;
}
activatedElementsTable();
}
});
elementsTablePanel.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
return;
}
activatedElementsTable();
e.consume();
}
});
attributesTablePanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) {
return;
}
activatedAttributesTable();
}
});
attributesTablePanel.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
return;
}
activatedAttributesTable();
e.consume();
}
});
elementsTablePanel.addSeekListener(seekListener); elementsTablePanel.addSeekListener(seekListener);
attributesTablePanel.addSeekListener(seekListener); attributesTablePanel.addSeekListener(seekListener);
@ -393,16 +360,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
.enabledWhen(this::hasSingleLink) .enabledWhen(this::hasSingleLink)
.onAction(this::activatedFollowLink) .onAction(this::activatedFollowLink)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
// TODO: These are a stopgap until the plot column header provides nav
actionStepBackward = StepSnapBackwardAction.builder(plugin)
.enabledWhen(this::isStepBackwardEnabled)
.onAction(this::activatedStepBackward)
.buildAndInstallLocal(this);
actionStepForward = StepSnapForwardAction.builder(plugin)
.enabledWhen(this::isStepForwardEnabled)
.onAction(this::activatedStepForward)
.buildAndInstallLocal(this);
} }
private void activatedElementsTable() { private void activatedElementsTable() {
@ -480,44 +437,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED); setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
} }
private boolean isStepBackwardEnabled(ActionContext ignored) {
if (current.getTrace() == null) {
return false;
}
if (!current.getTime().isSnapOnly()) {
return true;
}
if (current.getSnap() <= 0) {
return false;
}
return true;
}
private void activatedStepBackward(ActionContext ignored) {
if (current.getTime().isSnapOnly()) {
traceManager.activateSnap(current.getSnap() - 1);
}
else {
traceManager.activateSnap(current.getSnap());
}
}
private boolean isStepForwardEnabled(ActionContext ignored) {
Trace curTrace = current.getTrace();
if (curTrace == null) {
return false;
}
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
if (maxSnap == null || current.getSnap() >= maxSnap) {
return false;
}
return true;
}
private void activatedStepForward(ActionContext ignored) {
traceManager.activateSnap(current.getSnap() + 1);
}
@Override @Override
public JComponent getComponent() { public JComponent getComponent() {
return mainPanel; return mainPanel;
@ -533,7 +452,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
return null; return null;
} }
TraceObject parent = trace.getObjectManager().getObjectByCanonicalPath(parentPath); TraceObject parent = trace.getObjectManager().getObjectByCanonicalPath(parentPath);
// TODO: Require parent to be a canonical container? // Should we require parent to be a canonical container?
if (parent == null) { if (parent == null) {
return null; return null;
} }

View file

@ -49,6 +49,86 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
}); });
} }
public interface ValueProperty<T> {
public Class<T> getType();
public ValueRow getRow();
public T getValue();
public String getDisplay();
public String getHtmlDisplay();
public String getToolTip();
public boolean isModified();
}
public static abstract class ValueDerivedProperty<T> implements ValueProperty<T> {
protected final ValueRow row;
protected final Class<T> type;
public ValueDerivedProperty(ValueRow row, Class<T> type) {
this.row = row;
this.type = type;
}
@Override
public ValueRow getRow() {
return row;
}
@Override
public Class<T> getType() {
return type;
}
}
public record ValueAttribute<T> (ValueRow row, String name, Class<T> type)
implements ValueProperty<T> {
public TraceObjectValue getEntry() {
return row.getAttributeEntry(name);
}
@Override
public ValueRow getRow() {
return row;
}
@Override
public Class<T> getType() {
return type;
}
@Override
public T getValue() {
TraceObjectValue entry = row.getAttributeEntry(name);
return entry == null || !type.isInstance(entry.getValue()) ? null
: type.cast(entry.getValue());
}
@Override
public String getDisplay() {
return row.getAttributeDisplay(name);
}
@Override
public String getHtmlDisplay() {
return row.getAttributeHtmlDisplay(name);
}
@Override
public String getToolTip() {
return row.getAttributeToolTip(name);
}
@Override
public boolean isModified() {
return row.isAttributeModified(name);
}
}
public interface ValueRow { public interface ValueRow {
String getKey(); String getKey();
@ -79,7 +159,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
*/ */
boolean isModified(); boolean isModified();
TraceObjectValue getAttribute(String attributeName); default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
return new ValueAttribute<>(this, attributeName, type);
}
TraceObjectValue getAttributeEntry(String attributeName);
String getAttributeDisplay(String attributeName); String getAttributeDisplay(String attributeName);
@ -143,7 +227,7 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
} }
@Override @Override
public TraceObjectValue getAttribute(String attributeName) { public TraceObjectValue getAttributeEntry(String attributeName) {
return null; return null;
} }
@ -196,28 +280,28 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
} }
@Override @Override
public TraceObjectValue getAttribute(String attributeName) { public TraceObjectValue getAttributeEntry(String attributeName) {
return object.getAttribute(getSnap(), attributeName); return object.getAttribute(getSnap(), attributeName);
} }
@Override @Override
public String getAttributeDisplay(String attributeName) { public String getAttributeDisplay(String attributeName) {
return display.getEdgeDisplay(getAttribute(attributeName)); return display.getEdgeDisplay(getAttributeEntry(attributeName));
} }
@Override @Override
public String getAttributeHtmlDisplay(String attributeName) { public String getAttributeHtmlDisplay(String attributeName) {
return display.getEdgeHtmlDisplay(getAttribute(attributeName)); return display.getEdgeHtmlDisplay(getAttributeEntry(attributeName));
} }
@Override @Override
public String getAttributeToolTip(String attributeName) { public String getAttributeToolTip(String attributeName) {
return display.getEdgeToolTip(getAttribute(attributeName)); return display.getEdgeToolTip(getAttributeEntry(attributeName));
} }
@Override @Override
public boolean isAttributeModified(String attributeName) { public boolean isAttributeModified(String attributeName) {
return isValueModified(getAttribute(attributeName)); return isValueModified(getAttributeEntry(attributeName));
} }
} }
@ -269,21 +353,21 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
} }
} }
static class AutoAttributeColumn extends TraceValueObjectAttributeColumn { static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {
public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx, public static TraceValueObjectAttributeColumn<?> fromSchema(SchemaContext ctx,
AttributeSchema attributeSchema) { AttributeSchema attributeSchema) {
String name = attributeSchema.getName(); String name = attributeSchema.getName();
Class<?> type = computeColumnType(ctx, attributeSchema); Class<?> type = computeColumnType(ctx, attributeSchema);
return new AutoAttributeColumn(name, type); return new AutoAttributeColumn<>(name, type);
} }
public AutoAttributeColumn(String attributeName, Class<?> attributeType) { public AutoAttributeColumn(String attributeName, Class<T> attributeType) {
super(attributeName, attributeType); super(attributeName, attributeType);
} }
} }
// TODO: Save and restore these between sessions, esp., their settings // TODO: Save and restore these between sessions, esp., their settings
private Map<ColKey, TraceValueObjectAttributeColumn> columnCache = new HashMap<>(); private Map<ColKey, TraceValueObjectAttributeColumn<?>> columnCache = new HashMap<>();
protected ObjectTableModel(Plugin plugin) { protected ObjectTableModel(Plugin plugin) {
super("Object Model", plugin); super("Object Model", plugin);
@ -454,14 +538,14 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
int count = getColumnCount(); int count = getColumnCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i); DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
if (column instanceof TraceValueObjectAttributeColumn attrCol) { if (column instanceof TraceValueObjectAttributeColumn<?> attrCol) {
attrCol.setDiffColor(diffColor); attrCol.setDiffColor(diffColor);
} }
else if (column instanceof TraceValueValColumn valCol) { else if (column instanceof TraceValueValColumn valCol) {
valCol.setDiffColor(diffColor); valCol.setDiffColor(diffColor);
} }
} }
for (TraceValueObjectAttributeColumn column : columnCache.values()) { for (TraceValueObjectAttributeColumn<?> column : columnCache.values()) {
column.setDiffColor(diffColor); column.setDiffColor(diffColor);
} }
} }
@ -471,14 +555,14 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
int count = getColumnCount(); int count = getColumnCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i); DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
if (column instanceof TraceValueObjectAttributeColumn attrCol) { if (column instanceof TraceValueObjectAttributeColumn<?> attrCol) {
attrCol.setDiffColorSel(diffColorSel); attrCol.setDiffColorSel(diffColorSel);
} }
else if (column instanceof TraceValueValColumn valCol) { else if (column instanceof TraceValueValColumn valCol) {
valCol.setDiffColorSel(diffColorSel); valCol.setDiffColorSel(diffColorSel);
} }
} }
for (TraceValueObjectAttributeColumn column : columnCache.values()) { for (TraceValueObjectAttributeColumn<?> column : columnCache.values()) {
column.setDiffColorSel(diffColorSel); column.setDiffColorSel(diffColorSel);
} }
} }

View file

@ -27,7 +27,7 @@ import ghidra.trace.model.Trace;
import ghidra.util.table.column.GColumnRenderer; import ghidra.util.table.column.GColumnRenderer;
public class TraceValueLifePlotColumn public class TraceValueLifePlotColumn
extends AbstractDynamicTableColumn<ValueRow, SpanSet<Long,?>, Trace> { extends AbstractDynamicTableColumn<ValueRow, SpanSet<Long, ?>, Trace> {
private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>(); private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>();
private final RangeCursorTableHeaderRenderer<Long> headerRenderer = private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
@ -45,7 +45,7 @@ public class TraceValueLifePlotColumn
} }
@Override @Override
public GColumnRenderer<SpanSet<Long,?>> getColumnRenderer() { public GColumnRenderer<SpanSet<Long, ?>> getColumnRenderer() {
return cellRenderer; return cellRenderer;
} }

View file

@ -15,18 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.model.columns; package ghidra.app.plugin.core.debug.gui.model.columns;
import java.awt.Color; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import java.awt.Component;
import java.util.Comparator;
import java.util.function.Function;
import javax.swing.JTable;
import docking.widgets.table.*;
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
import docking.widgets.table.sort.DefaultColumnComparator;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet; import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
@ -37,49 +26,10 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema; import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
public class TraceValueObjectAttributeColumn public class TraceValueObjectAttributeColumn<T>
extends AbstractDynamicTableColumn<ValueRow, ValueRow, Trace> { extends TraceValueObjectPropertyColumn<T> {
public class AttributeRenderer extends AbstractGColumnRenderer<ValueRow>
implements ColorsModified.InTable {
{
setHTMLRenderingEnabled(true);
}
@Override
public String getFilterString(ValueRow t, Settings settings) {
return t.getAttributeDisplay(attributeName);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
ValueRow row = (ValueRow) data.getValue();
setText(row.getAttributeHtmlDisplay(attributeName));
setToolTipText(row.getAttributeToolTip(attributeName));
setForeground(getForegroundFor(data.getTable(), row.isAttributeModified(attributeName),
data.isSelected()));
return this;
}
@Override
public Color getDiffForeground(JTable table) {
return diffColor;
}
@Override
public Color getDiffSelForeground(JTable table) {
return diffColorSel;
}
}
public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) { public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) {
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema()); TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
@ -105,18 +55,11 @@ public class TraceValueObjectAttributeColumn
return type; return type;
} }
private final String attributeName; protected final String attributeName;
private final Class<?> attributeType;
private final AttributeRenderer renderer = new AttributeRenderer();
private final Comparator<ValueRow> comparator;
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED; public TraceValueObjectAttributeColumn(String attributeName, Class<T> attributeType) {
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL; super(attributeType);
public TraceValueObjectAttributeColumn(String attributeName, Class<?> attributeType) {
this.attributeName = attributeName; this.attributeName = attributeName;
this.attributeType = attributeType;
this.comparator = newTypedComparator();
} }
@Override @Override
@ -131,43 +74,7 @@ public class TraceValueObjectAttributeColumn
} }
@Override @Override
public ValueRow getValue(ValueRow rowObject, Settings settings, Trace data, public ValueProperty<T> getProperty(ValueRow row) {
ServiceProvider serviceProvider) throws IllegalArgumentException { return row.getAttribute(attributeName, propertyType);
return rowObject;
}
@Override
public GColumnRenderer<ValueRow> getColumnRenderer() {
return renderer;
}
@Override
public Comparator<ValueRow> getComparator(DynamicColumnTableModel<?> model, int columnIndex) {
return comparator == null ? null
: comparator.thenComparing(
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
}
public Object getAttributeValue(ValueRow row) {
TraceObjectValue edge = row.getAttribute(attributeName);
return edge == null ? null : edge.getValue();
}
protected <C extends Comparable<C>> Comparator<ValueRow> newTypedComparator() {
if (Comparable.class.isAssignableFrom(attributeType)) {
@SuppressWarnings("unchecked")
Class<C> cls = (Class<C>) attributeType.asSubclass(Comparable.class);
Function<ValueRow, C> keyExtractor = r -> cls.cast(getAttributeValue(r));
return Comparator.comparing(keyExtractor, new DefaultColumnComparator());
}
return null; // Opt for the default filter-string-based comparator
}
public void setDiffColor(Color diffColor) {
this.diffColor = diffColor;
}
public void setDiffColorSel(Color diffColorSel) {
this.diffColorSel = diffColorSel;
} }
} }

View file

@ -0,0 +1,168 @@
/* ###
* 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.model.columns;
import java.awt.*;
import java.util.Comparator;
import java.util.function.Function;
import javax.swing.JTable;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.table.*;
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
import docking.widgets.table.sort.DefaultColumnComparator;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Trace;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
public abstract class TraceValueObjectPropertyColumn<T>
extends AbstractDynamicTableColumn<ValueRow, ValueProperty<T>, Trace> {
public class PropertyRenderer extends AbstractGColumnRenderer<ValueProperty<T>>
implements ColorsModified.InTable {
{
setHTMLRenderingEnabled(true);
}
@Override
public String getFilterString(ValueProperty<T> p, Settings settings) {
return p.getDisplay();
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
@SuppressWarnings("unchecked")
ValueProperty<T> p = (ValueProperty<T>) data.getValue();
setText(p.getHtmlDisplay());
setToolTipText(p.getToolTip());
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
return this;
}
@Override
public Color getDiffForeground(JTable p) {
return diffColor;
}
@Override
public Color getDiffSelForeground(JTable p) {
return diffColorSel;
}
}
public class BooleanPropertyRenderer extends PropertyRenderer {
protected GCheckBox cb;
{
setLayout(new BorderLayout());
cb = new GCheckBox();
cb.setHorizontalAlignment(CENTER);
cb.setOpaque(false);
add(cb);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
@SuppressWarnings("unchecked")
ValueProperty<T> property = (ValueProperty<T>) data.getValue();
T value = property.getValue();
if (value instanceof Boolean b) {
cb.setVisible(true);
cb.setSelected(b);
setText("");
}
else {
cb.setVisible(false);
}
return this;
}
@Override
public void validate() {
synchronized (getTreeLock()) {
validateTree();
}
}
}
protected final Class<T> propertyType;
private final GColumnRenderer<ValueProperty<T>> renderer;
private final Comparator<ValueProperty<T>> comparator;
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
public TraceValueObjectPropertyColumn(Class<T> propertyType) {
this.propertyType = propertyType;
this.comparator = newTypedComparator();
this.renderer = createRenderer();
}
public GColumnRenderer<ValueProperty<T>> createRenderer() {
if (propertyType == Boolean.class) {
return new BooleanPropertyRenderer();
}
return new PropertyRenderer();
}
@Override
public GColumnRenderer<ValueProperty<T>> getColumnRenderer() {
return renderer;
}
@Override
public Comparator<ValueProperty<T>> getComparator(DynamicColumnTableModel<?> model,
int columnIndex) {
return comparator == null ? null
: comparator.thenComparing(
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
}
public abstract ValueProperty<T> getProperty(ValueRow row);
@Override
public ValueProperty<T> getValue(ValueRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return getProperty(rowObject);
}
protected <C extends Comparable<C>> Comparator<ValueProperty<T>> newTypedComparator() {
if (Comparable.class.isAssignableFrom(propertyType)) {
@SuppressWarnings("unchecked")
Class<C> cls = (Class<C>) propertyType.asSubclass(Comparable.class);
Function<ValueProperty<T>, C> keyExtractor = r -> cls.cast(r.getValue());
return Comparator.comparing(keyExtractor, new DefaultColumnComparator());
}
return null; // Opt for the default filter-string-based comparator
}
public void setDiffColor(Color diffColor) {
this.diffColor = diffColor;
}
public void setDiffColorSel(Color diffColorSel) {
this.diffColorSel = diffColorSel;
}
}

View file

@ -18,17 +18,20 @@ package ghidra.app.plugin.core.debug.gui.stack;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import docking.ActionContext;
import docking.widgets.table.AbstractDynamicTableColumn; import docking.widgets.table.AbstractDynamicTableColumn;
import docking.widgets.table.TableColumnDescriptor; import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.model.*; import ghidra.app.plugin.core.debug.gui.model.*;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn; import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn; import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.dbg.target.TargetStack; import ghidra.dbg.target.TargetStack;
import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetStackFrame;
@ -40,11 +43,13 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback;
import utilities.util.SuppressableCallback.Suppression; import utilities.util.SuppressableCallback.Suppression;
public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelectionListener { public class DebuggerStackPanel extends ObjectsTablePanel
implements ListSelectionListener, CellActivationListener {
private static class FrameLevelColumn extends TraceValueKeyColumn { private static class FrameLevelColumn extends TraceValueKeyColumn {
@Override @Override
@ -58,7 +63,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
} }
} }
private static class FramePcColumn extends TraceValueObjectAttributeColumn { private static class FramePcColumn extends TraceValueObjectAttributeColumn<Address> {
public FramePcColumn() { public FramePcColumn() {
super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class); super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class);
} }
@ -80,7 +85,8 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
@Override @Override
public Function getValue(ValueRow rowObject, Settings settings, Trace data, public Function getValue(ValueRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException { ServiceProvider serviceProvider) throws IllegalArgumentException {
TraceObjectValue value = rowObject.getAttribute(TargetStackFrame.PC_ATTRIBUTE_NAME); TraceObjectValue value =
rowObject.getAttributeEntry(TargetStackFrame.PC_ATTRIBUTE_NAME);
return value == null ? null : provider.getFunction((Address) value.getValue()); return value == null ? null : provider.getFunction((Address) value.getValue());
} }
} }
@ -96,13 +102,14 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
descriptor.addVisibleColumn(new FrameLevelColumn()); descriptor.addVisibleColumn(new FrameLevelColumn());
descriptor.addVisibleColumn(new FramePcColumn()); descriptor.addVisibleColumn(new FramePcColumn());
descriptor.addVisibleColumn(new FrameFunctionColumn()); descriptor.addVisibleColumn(new FrameFunctionColumn());
// TODO: Comment column?
return descriptor; return descriptor;
} }
} }
private final DebuggerStackProvider provider; private final DebuggerStackProvider provider;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@AutoServiceConsumed @AutoServiceConsumed
protected DebuggerTraceManagerService traceManager; protected DebuggerTraceManagerService traceManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -122,6 +129,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
setShowHidden(false); setShowHidden(false);
addSelectionListener(this); addSelectionListener(this);
addCellActivationListener(this);
} }
@Override @Override
@ -129,7 +137,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
return new StackTableModel(plugin); return new StackTableModel(plugin);
} }
public ActionContext getActionContext() { public DebuggerObjectActionContext getActionContext() {
return myActionContext; return myActionContext;
} }
@ -137,8 +145,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
if (object == null) { if (object == null) {
return ModelQuery.EMPTY; return ModelQuery.EMPTY;
} }
TargetObjectSchema rootSchema = object.getRoot() TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
.getTargetSchema();
List<String> stackPath = rootSchema List<String> stackPath = rootSchema
.searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList()); .searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList());
if (stackPath == null) { if (stackPath == null) {
@ -176,4 +183,21 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild())); cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
} }
} }
@Override
public void cellActivated(JTable table) {
if (listingService == null) {
return;
}
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object value = table.getValueAt(row, col);
if (!(value instanceof ValueProperty<?> property)) {
return;
}
Object propVal = property.getValue();
if (propVal instanceof Address address) {
listingService.goTo(address, true);
}
}
} }

View file

@ -22,11 +22,6 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.thread.TraceThread;
@PluginInfo( @PluginInfo(
shortDescription = "Debugger stack view", shortDescription = "Debugger stack view",

View file

@ -110,10 +110,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
final ActionContext context;
if (isLegacy(current.getTrace())) { if (isLegacy(current.getTrace())) {
return legacyPanel.getActionContext(); context = legacyPanel.getActionContext();
}
else {
context = panel.getActionContext();
} }
ActionContext context = panel.getActionContext();
if (context != null) { if (context != null) {
return context; return context;
} }

View file

@ -255,7 +255,7 @@ public enum DebuggerStaticMappingProposals {
protected static Set<String> getLikelyModulesFromName(TraceMemoryRegion region) { protected static Set<String> getLikelyModulesFromName(TraceMemoryRegion region) {
String key; String key;
try { try {
List<String> path = PathUtils.parse(region.getName()); List<String> path = PathUtils.parse(region.getPath());
key = PathUtils.getKey(path); key = PathUtils.getKey(path);
if (PathUtils.isIndex(key)) { if (PathUtils.isIndex(key)) {
key = PathUtils.parseIndex(key); key = PathUtils.parseIndex(key);

View file

@ -40,11 +40,14 @@ import org.junit.runner.Description;
import docking.ActionContext; import docking.ActionContext;
import docking.action.ActionContextProvider; import docking.action.ActionContextProvider;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.table.DynamicTableColumn;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.Unique; import generic.Unique;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectPropertyColumn;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.*; import ghidra.app.plugin.core.debug.service.model.*;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
@ -54,6 +57,7 @@ import ghidra.dbg.model.AbstractTestTargetRegisterBank;
import ghidra.dbg.model.TestDebuggerModelBuilder; import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.testutil.DebuggerModelTestUtils; import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
@ -486,6 +490,19 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
runSwing(() -> nav.setSelection(sel)); runSwing(() -> nav.setSelection(sel));
} }
protected Object rowColVal(ValueRow row, DynamicTableColumn<ValueRow, ?, Trace> col) {
if (col instanceof TraceValueObjectPropertyColumn<?> attrCol) {
return attrCol.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool).getValue();
}
Object value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
return value;
}
protected <T> String rowColDisplay(ValueRow row, DynamicTableColumn<ValueRow, T, Trace> col) {
T value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
return col.getColumnRenderer().getFilterString(value, SettingsImpl.NO_SETTINGS);
}
protected static LocationTrackingSpec getLocationTrackingSpec(String name) { protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpecFactory.fromConfigName(name); return LocationTrackingSpecFactory.fromConfigName(name);
} }

View file

@ -0,0 +1,391 @@
/* ###
* 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.memory;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerLegacyRegionsPanel.RegionTableColumns;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class)
public class DebuggerRegionsProviderLegacyTest extends AbstractGhidraHeadedDebuggerGUITest {
DebuggerRegionsProvider provider;
protected TraceMemoryRegion regionExeText;
protected TraceMemoryRegion regionExeData;
protected TraceMemoryRegion regionLibText;
protected TraceMemoryRegion regionLibData;
protected MemoryBlock blockExeText;
protected MemoryBlock blockExeData;
@Before
public void setUpRegionsTest() throws Exception {
addPlugin(tool, DebuggerRegionsPlugin.class);
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
}
protected void addRegions() throws Exception {
TraceMemoryManager mm = tb.trace.getMemoryManager();
try (UndoableTransaction tid = tb.startTransaction()) {
regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0,
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0,
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0,
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0,
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
}
protected void addBlocks() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
Memory mem = program.getMemory();
blockExeText = mem.createInitializedBlock(".text", tb.addr(0x00400000), 0x100, (byte) 0,
monitor, false);
blockExeData = mem.createInitializedBlock(".data", tb.addr(0x00600000), 0x80, (byte) 0,
monitor, false);
}
}
@Test
public void testNoTraceEmpty() throws Exception {
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testActivateEmptyTraceEmpty() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testAddThenActivateTracePopulates() throws Exception {
createTrace();
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
assertEquals("Memory[bin:.text]", row.getName());
assertEquals(tb.addr(0x00400000), row.getMinAddress());
assertEquals(tb.addr(0x0040ffff), row.getMaxAddress());
assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange());
assertEquals(0x10000, row.getLength());
assertEquals(0L, row.getCreatedSnap());
assertEquals("", row.getDestroyedSnap());
assertEquals(Lifespan.nowOn(0), row.getLifespan());
}
@Test
public void testActivateTraceThenAddPopulates() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
}
@Test
public void testDeleteRemoves() throws Exception {
createTrace();
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
try (UndoableTransaction tid = tb.startTransaction()) {
region.delete();
}
waitForDomainObject(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testUndoRedo() throws Exception {
createTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
undo(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
redo(tb.trace);
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
}
@Test
public void testAbort() throws Exception {
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
waitForDomainObject(tb.trace);
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
tid.abort();
}
waitForDomainObject(tb.trace);
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
}
@Test
public void testDoubleClickNavigates() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.START.ordinal(), 2);
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.END.ordinal(), 2);
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
}
@Test
public void testActionMapRegions() throws Exception {
assertFalse(provider.actionMapRegions.isEnabled());
createAndOpenTrace();
createAndOpenProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
addRegions();
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(provider.actionMapRegions.isEnabled());
addBlocks();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
program.setName("echo");
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(4, provider.legacyPanel.regionTable.getRowCount()));
// NB. Feature works "best" when all regions of modules are selected
// TODO: Test cases where feature works "worst"?
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
waitForSwing();
performEnabledAction(provider, provider.actionMapRegions, false);
DebuggerRegionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
List<RegionMapEntry> proposal = new ArrayList<>(propDialog.getTableModel().getModelData());
assertEquals(2, proposal.size());
RegionMapEntry entry;
// Table sorts by name by default.
// Names are file name followed by min address, so .text is first.
entry = proposal.get(0);
assertEquals(regionExeText, entry.getRegion());
assertEquals(blockExeText, entry.getBlock());
entry = proposal.get(1);
assertEquals(regionExeData, entry.getRegion());
assertEquals(blockExeData, entry.getBlock());
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class);
MemoryBlockRow row = blockDialog.getTableFilterPanel().getSelectedItem();
assertEquals(blockExeText, row.getBlock());
pressButtonByText(blockDialog, "OK", true);
assertEquals(blockExeData, entry.getBlock()); // Unchanged
// TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings =
tb.trace.getStaticMappingManager().getAllEntries();
assertEquals(0, mappings.size());
pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace);
assertEquals(2, mappings.size());
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
TraceStaticMapping sm;
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength());
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00600000", sm.getStaticAddress());
assertEquals(0x80, sm.getLength());
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
assertFalse(mit.hasNext());
}
// TODO: testActionMapRegionsTo
// TODO: testActionMapRegionTo
@Test
public void testActionSelectAddresses() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
provider.setSelectedRegions(Set.of(region));
waitForSwing();
performEnabledAction(provider, provider.actionSelectAddresses, true);
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
new AddressSet(listing.getSelection())));
}
@Test
public void testActionSelectRows() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace();
TraceMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager();
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
// NB. Table is debounced
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
new AddressSet(listing.getSelection())));
waitForSwing();
performEnabledAction(listing, provider.actionSelectRows, true);
waitForPass(
() -> assertEquals(Set.of(row), Set.copyOf(provider.legacyPanel.getSelectedRows())));
}
}

View file

@ -1,63 +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.memory;
import java.io.IOException;
import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class)
public class DebuggerRegionsProviderObjectTest extends DebuggerRegionsProviderTest {
protected SchemaContext ctx;
@Override
protected void createTrace(String langID) throws IOException {
super.createTrace(langID);
try {
activateObjectsMode();
}
catch (Exception e) {
throw new AssertionError(e);
}
}
public void activateObjectsMode() throws Exception {
ctx = XmlSchemaContext.deserialize("" + //
"<context>" + //
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Memory' schema='RegionContainer' />" + //
" </schema>" + //
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Region' />" + //
" </schema>" + //
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='MemoryRegion' />" + //
" </schema>" + //
"</context>");
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
}
}
}

View file

@ -17,61 +17,118 @@ package ghidra.app.plugin.core.debug.gui.memory;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*; import java.util.*;
import org.junit.Before; import org.junit.*;
import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import generic.Unique; import docking.widgets.table.DynamicTableColumn;
import generic.test.category.NightlyCategory; import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog; import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow; import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns; import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsProvider.RegionTableColumns; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.app.services.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.address.AddressSet; import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.memory.*; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTable;
@Category(NightlyCategory.class) @Category(NightlyCategory.class)
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest { public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
DebuggerRegionsProvider provider; DebuggerRegionsProvider provider;
protected TraceMemoryRegion regionExeText; TraceObjectMemoryRegion regionExeText;
protected TraceMemoryRegion regionExeData; TraceObjectMemoryRegion regionExeData;
protected TraceMemoryRegion regionLibText; TraceObjectMemoryRegion regionLibText;
protected TraceMemoryRegion regionLibData; TraceObjectMemoryRegion regionLibData;
protected MemoryBlock blockExeText; MemoryBlock blockExeText;
protected MemoryBlock blockExeData; MemoryBlock blockExeData;
@Before protected SchemaContext ctx;
public void setUpRegionsTest() throws Exception {
addPlugin(tool, DebuggerRegionsPlugin.class); @Override
provider = waitForComponentProvider(DebuggerRegionsProvider.class); protected void createTrace(String langID) throws IOException {
super.createTrace(langID);
try {
activateObjectsMode();
}
catch (Exception e) {
throw new AssertionError(e);
}
}
public void activateObjectsMode() throws Exception {
ctx = XmlSchemaContext.deserialize("""
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Memory' schema='RegionContainer' />
</schema>
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Region' />
</schema>
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
<interface name='MemoryRegion' />
<attribute name='_display' schema='STRING' hidden='yes' />
<attribute name='_range' schema='RANGE' hidden='yes' />
<attribute name='_readable' schema='BOOL' hidden='yes' />
<attribute name='_writable' schema='BOOL' hidden='yes' />
<attribute name='_executable' schema='BOOL' hidden='yes' />
</schema>
</context>
""");
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
}
}
protected TraceObjectMemoryRegion addRegion(String name, long loaded, AddressRange range) {
boolean isData = name.endsWith(".data");
TraceObjectManager om = tb.trace.getObjectManager();
TraceObjectKeyPath memPath = TraceObjectKeyPath.parse("Memory");
Lifespan span = Lifespan.nowOn(loaded);
TraceObjectMemoryRegion region = Objects.requireNonNull(om.createObject(memPath.index(name))
.insert(span, ConflictResolution.TRUNCATE)
.getDestination(null)
.queryInterface(TraceObjectMemoryRegion.class));
TraceObject obj = region.getObject();
obj.setAttribute(span, TargetMemoryRegion.DISPLAY_ATTRIBUTE_NAME, name);
obj.setAttribute(span, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, range);
obj.setAttribute(span, TargetMemoryRegion.READABLE_ATTRIBUTE_NAME, true);
obj.setAttribute(span, TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME, isData);
obj.setAttribute(span, TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME, !isData);
return region;
} }
protected void addRegions() throws Exception { protected void addRegions() throws Exception {
TraceMemoryManager mm = tb.trace.getMemoryManager();
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0, regionExeText = addRegion("/bin/echo .text", 0, tb.range(0x55550000, 0x555500ff));
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); regionExeData = addRegion("/bin/echo .data", 0, tb.range(0x55750000, 0x5575007f));
regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0, regionLibText = addRegion("/lib/libc.so .text", 0, tb.range(0x7f000000, 0x7f0003ff));
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); regionLibData = addRegion("/lib/libc.so .data", 0, tb.range(0x7f100000, 0x7f10003f));
regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0,
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0,
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
} }
} }
@ -85,45 +142,83 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
} }
} }
protected void assertTableSize(int size) {
assertEquals(size, provider.panel.getAllItems().size());
}
protected void assertRow(int position, Object object, String name, Address start,
Address end, long length, String flags) {
ValueRow row = provider.panel.getAllItems().get(position);
DynamicTableColumn<ValueRow, ValueRow, Trace> nameCol =
provider.panel.getColumnByNameAndType("Name", ValueRow.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> startCol =
provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> endCol =
provider.panel.getColumnByNameAndType("End", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> lengthCol =
provider.panel.getColumnByNameAndType("Length", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> readCol =
provider.panel.getColumnByNameAndType("Read", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> writeCol =
provider.panel.getColumnByNameAndType("Write", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, ?, Trace> executeCol =
provider.panel.getColumnByNameAndType("Execute", ValueProperty.class).getValue();
assertSame(object, row.getValue().getValue());
assertEquals(name, rowColDisplay(row, nameCol));
assertEquals(start, rowColVal(row, startCol));
assertEquals(end, rowColVal(row, endCol));
assertEquals(length, rowColVal(row, lengthCol));
assertEquals(flags.contains("r"), rowColVal(row, readCol));
assertEquals(flags.contains("w"), rowColVal(row, writeCol));
assertEquals(flags.contains("x"), rowColVal(row, executeCol));
}
@Before
public void setUpRegionsProviderTest() throws Exception {
addPlugin(tool, DebuggerRegionsPlugin.class);
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
}
@After
public void testDownRegionsProviderTest() throws Exception {
traceManager.activate(DebuggerCoordinates.NOWHERE);
waitForSwing();
waitForTasks();
runSwing(() -> traceManager.closeAllTraces());
}
@Test @Test
public void testNoTraceEmpty() throws Exception { public void testNoTraceEmpty() throws Exception {
assertEquals(0, provider.regionTableModel.getModelData().size()); waitForPass(() -> assertTableSize(0));
} }
@Test @Test
public void testActivateEmptyTraceEmpty() throws Exception { public void testActivateEmptyTraceEmpty() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
assertEquals(0, provider.regionTableModel.getModelData().size()); waitForPass(() -> assertTableSize(0));
} }
@Test @Test
public void testAddThenActivateTracePopulates() throws Exception { public void testAddThenActivateTracePopulates() throws Exception {
createTrace(); createAndOpenTrace();
TraceMemoryRegion region; TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); waitForPass(() -> {
assertEquals(region, row.getRegion()); assertTableSize(1);
assertEquals("Memory[bin:.text]", row.getName()); assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
assertEquals(tb.addr(0x00400000), row.getMinAddress()); 0x10000, "rx");
assertEquals(tb.addr(0x0040ffff), row.getMaxAddress()); });
assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange());
assertEquals(0x10000, row.getLength());
assertEquals(0L, row.getCreatedSnap());
assertEquals("", row.getDestroyedSnap());
assertEquals(Lifespan.nowOn(0), row.getLifespan());
} }
@Test @Test
@ -131,65 +226,81 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
createAndOpenTrace(); createAndOpenTrace();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
TraceMemoryRegion region; waitForPass(() -> assertTableSize(0));
TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
waitForTasks();
waitForSwing(); waitForPass(() -> {
assertTableSize(1);
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
assertEquals(region, row.getRegion()); 0x10000, "rx");
});
} }
@Test @Test
public void testDeleteRemoves() throws Exception { public void testRemoveRegionRemovesFromTable() throws Exception {
createTrace(); createAndOpenTrace();
TraceMemoryRegion region; TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); waitForPass(() -> {
assertEquals(region, row.getRegion()); assertTableSize(1);
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
0x10000, "rx");
});
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
region.delete(); region.getObject().removeTree(Lifespan.nowOn(0));
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
waitForTasks();
assertEquals(0, provider.regionTableModel.getModelData().size()); waitForPass(() -> assertTableSize(0));
} }
@Test @Test
public void testUndoRedo() throws Exception { public void testUndoRedo() throws Exception {
createTrace(); createAndOpenTrace();
TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
Unique.assertOne(provider.regionTableModel.getModelData());
waitForPass(() -> {
assertTableSize(1);
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
0x10000, "rx");
});
undo(tb.trace); undo(tb.trace);
assertEquals(0, provider.regionTableModel.getModelData().size()); waitForDomainObject(tb.trace);
waitForTasks();
waitForPass(() -> assertTableSize(0));
redo(tb.trace); redo(tb.trace);
Unique.assertOne(provider.regionTableModel.getModelData()); waitForDomainObject(tb.trace);
waitForTasks();
waitForPass(() -> {
assertTableSize(1);
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
0x10000, "rx");
});
} }
@Test @Test
@ -197,17 +308,23 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
createAndOpenTrace(); createAndOpenTrace();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
Unique.assertOne(provider.regionTableModel.getModelData()); waitForTasks();
waitForPass(() -> {
assertTableSize(1);
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
tb.addr(0x0040ffff), 0x10000, "rx");
});
tid.abort(); tid.abort();
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(0, provider.regionTableModel.getModelData().size()); waitForTasks();
waitForPass(() -> assertTableSize(0));
} }
@Test @Test
@ -215,27 +332,31 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
addPlugin(tool, DebuggerListingPlugin.class); addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace(); createAndOpenTrace();
TraceMemoryRegion region; TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); waitForPass(() -> {
assertTableSize(1);
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
0x10000, "rx");
});
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); int startColIdx =
assertEquals(region, row.getRegion()); provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getKey();
int endColIdx = provider.panel.getColumnByNameAndType("End", ValueProperty.class).getKey();
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
clickTableCell(provider.regionTable, 0, RegionTableColumns.START.ordinal(), 2); clickTableCell(table, 0, startColIdx, 2);
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress())); waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
clickTableCell(provider.regionTable, 0, RegionTableColumns.END.ordinal(), 2); clickTableCell(table, 0, endColIdx, 2);
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress())); waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
} }
@ -260,15 +381,13 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
program.setName("echo"); program.setName("echo");
} }
waitForDomainObject(program); waitForDomainObject(program);
waitForPass(() -> assertEquals(4, provider.regionTable.getRowCount())); waitForPass(() -> assertTableSize(4));
// NB. Feature works "best" when all regions of modules are selected // NB. Feature works "best" when all regions of modules are selected
// TODO: Test cases where feature works "worst"? // TODO: Test cases where feature works "worst"?
provider.setSelectedRegions(Set.of(regionExeText, regionExeData)); provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
waitForSwing(); waitForSwing();
assertTrue(provider.actionMapRegions.isEnabled()); performEnabledAction(provider, provider.actionMapRegions, false);
performAction(provider.actionMapRegions, false);
DebuggerRegionMapProposalDialog propDialog = DebuggerRegionMapProposalDialog propDialog =
waitForDialogComponent(DebuggerRegionMapProposalDialog.class); waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
@ -277,16 +396,16 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(2, proposal.size()); assertEquals(2, proposal.size());
RegionMapEntry entry; RegionMapEntry entry;
// Table sorts by name by default. // Table sorts by name by default, so .data is first
// Names are file name followed by min address, so .text is first.
entry = proposal.get(0); entry = proposal.get(0);
assertEquals(regionExeText, entry.getRegion());
assertEquals(blockExeText, entry.getBlock());
entry = proposal.get(1);
assertEquals(regionExeData, entry.getRegion()); assertEquals(regionExeData, entry.getRegion());
assertEquals(blockExeData, entry.getBlock()); assertEquals(blockExeData, entry.getBlock());
entry = proposal.get(1);
assertEquals(regionExeText, entry.getRegion());
assertEquals(blockExeText, entry.getBlock());
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1); // Select the .text row
clickTableCell(propDialog.getTable(), 1, RegionMapTableColumns.CHOOSE.ordinal(), 1);
DebuggerBlockChooserDialog blockDialog = DebuggerBlockChooserDialog blockDialog =
waitForDialogComponent(DebuggerBlockChooserDialog.class); waitForDialogComponent(DebuggerBlockChooserDialog.class);
@ -294,7 +413,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(blockExeText, row.getBlock()); assertEquals(blockExeText, row.getBlock());
pressButtonByText(blockDialog, "OK", true); pressButtonByText(blockDialog, "OK", true);
assertEquals(blockExeData, entry.getBlock()); // Unchanged assertEquals(blockExeText, entry.getBlock()); // Unchanged
// TODO: Test the changed case // TODO: Test the changed case
Collection<? extends TraceStaticMapping> mappings = Collection<? extends TraceStaticMapping> mappings =
@ -304,54 +423,50 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
pressButtonByText(propDialog, "OK", true); pressButtonByText(propDialog, "OK", true);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(2, mappings.size()); assertEquals(2, mappings.size());
// Ordered by db key. Thus, in order added
Iterator<? extends TraceStaticMapping> mit = mappings.iterator(); Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
TraceStaticMapping sm; TraceStaticMapping sm;
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength());
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
sm = mit.next(); sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan()); assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00600000", sm.getStaticAddress()); assertEquals("ram:00600000", sm.getStaticAddress());
assertEquals(0x80, sm.getLength()); assertEquals(0x80, sm.getLength());
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress()); assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
sm = mit.next();
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
assertEquals("ram:00400000", sm.getStaticAddress());
assertEquals(0x100, sm.getLength());
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
assertFalse(mit.hasNext()); assertFalse(mit.hasNext());
} }
// TODO: testActionMapRegionsTo
// TODO: testActionMapRegionTo
@Test @Test
public void testActionSelectAddresses() throws Exception { public void testActionSelectAddresses() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class); addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace(); createAndOpenTrace();
TraceMemoryRegion region; TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForTasks();
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); waitForPass(() -> {
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); assertTableSize(1);
assertEquals(region, row.getRegion()); assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
assertFalse(tb.trace.getProgramView().getMemory().isEmpty()); tb.addr(0x0040ffff), 0x10000, "rx");
});
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
provider.setSelectedRegions(Set.of(region)); provider.setSelectedRegions(Set.of(region));
waitForSwing(); waitForSwing();
assertTrue(provider.actionSelectAddresses.isEnabled()); performEnabledAction(provider, provider.actionSelectAddresses, true);
performAction(provider.actionSelectAddresses);
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)), waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
new AddressSet(listing.getSelection()))); new AddressSet(listing.getSelection())));
@ -362,33 +477,33 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
addPlugin(tool, DebuggerListingPlugin.class); addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
createTrace(); createAndOpenTrace();
TraceMemoryRegion region; TraceObjectMemoryRegion region;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryManager mm = tb.trace.getMemoryManager(); region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
} }
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); waitForPass(() -> {
// NB. Table is debounced assertTableSize(1);
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
assertEquals(region, row.getRegion()); tb.addr(0x0040ffff), 0x10000, "rx");
assertFalse(tb.trace.getProgramView().getMemory().isEmpty()); });
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321)))); listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)), waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
new AddressSet(listing.getSelection()))); new AddressSet(listing.getSelection())));
waitForSwing(); waitForSwing();
assertTrue(provider.actionSelectRows.isEnabled()); performEnabledAction(listing, provider.actionSelectRows, true);
performAction(provider.actionSelectRows);
waitForPass(() -> assertEquals(Set.of(row), Set.copyOf(provider.getSelectedRows()))); waitForPass(() -> {
List<ValueRow> allItems = provider.panel.getAllItems();
assertEquals(Set.of(allItems.get(0)), Set.copyOf(provider.panel.getSelectedItems()));
});
} }
} }

View file

@ -0,0 +1,24 @@
/* ###
* 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.model;
import ghidra.util.table.GhidraTable;
public class QueryPanelTestHelper {
public static GhidraTable getTable(AbstractQueryTablePanel<?, ?> panel) {
return panel.table;
}
}

View file

@ -27,8 +27,8 @@ import org.junit.*;
import docking.widgets.table.DynamicTableColumn; import docking.widgets.table.DynamicTableColumn;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
@ -39,7 +39,6 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.docking.settings.SettingsImpl;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@ -214,23 +213,15 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
assertEquals(size, stackProvider.panel.getAllItems().size()); assertEquals(size, stackProvider.panel.getAllItems().size());
} }
protected Object rowColVal(ValueRow row, DynamicTableColumn<ValueRow, ?, Trace> col) {
if (col instanceof TraceValueObjectAttributeColumn attrCol) {
return attrCol.getAttributeValue(row);
}
Object value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
return value;
}
protected void assertRow(int level, Address pcVal, Function func) { protected void assertRow(int level, Address pcVal, Function func) {
ValueRow row = stackProvider.panel.getAllItems().get(level); ValueRow row = stackProvider.panel.getAllItems().get(level);
DynamicTableColumn<ValueRow, String, Trace> levelCol = DynamicTableColumn<ValueRow, String, Trace> levelCol =
stackProvider.panel.getColumnByNameAndType("Level", String.class); stackProvider.panel.getColumnByNameAndType("Level", String.class).getValue();
DynamicTableColumn<ValueRow, ValueRow, Trace> pcCol = DynamicTableColumn<ValueRow, ?, Trace> pcCol =
stackProvider.panel.getColumnByNameAndType("PC", ValueRow.class); stackProvider.panel.getColumnByNameAndType("PC", ValueProperty.class).getValue();
DynamicTableColumn<ValueRow, Function, Trace> funcCol = DynamicTableColumn<ValueRow, Function, Trace> funcCol =
stackProvider.panel.getColumnByNameAndType("Function", Function.class); stackProvider.panel.getColumnByNameAndType("Function", Function.class).getValue();
assertEquals(PathUtils.makeKey(PathUtils.makeIndex(level)), rowColVal(row, levelCol)); assertEquals(PathUtils.makeKey(PathUtils.makeIndex(level)), rowColVal(row, levelCol));
assertEquals(pcVal, rowColVal(row, pcCol)); assertEquals(pcVal, rowColVal(row, pcCol));

View file

@ -356,8 +356,8 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
DBTrace trace = object.getTrace(); DBTrace trace = object.getTrace();
switch (key) { switch (key) {
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME: case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockRange(this, // NB. old/newValue are null here. The CREATED event just has the new entry.
(AddressRange) oldValue, (AddressRange) newValue); trace.updateViewsRefreshBlocks();
return; return;
case TargetObject.DISPLAY_ATTRIBUTE_NAME: case TargetObject.DISPLAY_ATTRIBUTE_NAME:
trace.updateViewsChangeRegionBlockName(this); trace.updateViewsChangeRegionBlockName(this);

View file

@ -44,7 +44,7 @@ public abstract class AbstractDBTraceProgramViewMemory
protected final DBTraceProgramView program; protected final DBTraceProgramView program;
protected final DBTraceMemoryManager memoryManager; protected final DBTraceMemoryManager memoryManager;
protected AddressSetView addressSet; protected volatile AddressSetView addressSet;
protected boolean forceFullView = false; protected boolean forceFullView = false;
protected long snap; protected long snap;
@ -79,7 +79,7 @@ public abstract class AbstractDBTraceProgramViewMemory
} }
} }
protected void computeFullAdddressSet() { protected synchronized void computeFullAdddressSet() {
AddressSet temp = new AddressSet(); AddressSet temp = new AddressSet();
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress())); forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
addressSet = temp; addressSet = temp;
@ -125,17 +125,17 @@ public abstract class AbstractDBTraceProgramViewMemory
} }
@Override @Override
public synchronized AddressSetView getLoadedAndInitializedAddressSet() { public AddressSetView getLoadedAndInitializedAddressSet() {
return addressSet; return addressSet;
} }
@Override @Override
public synchronized AddressSetView getAllInitializedAddressSet() { public AddressSetView getAllInitializedAddressSet() {
return addressSet; return addressSet;
} }
@Override @Override
public synchronized AddressSetView getInitializedAddressSet() { public AddressSetView getInitializedAddressSet() {
return addressSet; return addressSet;
} }
@ -230,7 +230,7 @@ public abstract class AbstractDBTraceProgramViewMemory
} }
@Override @Override
public synchronized long getSize() { public long getSize() {
return addressSet.getNumAddresses(); return addressSet.getNumAddresses();
} }

View file

@ -74,11 +74,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
} }
@Override @Override
protected void recomputeAddressSet() { protected synchronized void recomputeAddressSet() {
AddressSet temp = new AddressSet(); AddressSet temp = new AddressSet();
// TODO: Performance test this // TODO: Performance test this
forVisibleRegions(reg -> temp.add(reg.getRange())); forVisibleRegions(reg -> temp.add(reg.getRange()));
addressSet = temp; addressSet = temp;
System.err.println("Recomputed: " + temp);
} }
protected MemoryBlock getRegionBlock(TraceMemoryRegion region) { protected MemoryBlock getRegionBlock(TraceMemoryRegion region) {
@ -139,7 +140,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange, public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange,
AddressRange newRange) { AddressRange newRange) {
// TODO: update cached block? Nothing to update.
changeRange(oldRange, newRange); changeRange(oldRange, newRange);
} }

View file

@ -258,12 +258,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
rootSchema = schema; rootSchema = schema;
} }
protected void emitValueCreated(DBTraceObject parent, InternalTraceObjectValue entry) {
if (parent == null) {
// Don't need event for root value created
return;
}
parent.emitEvents(
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
}
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
DBTraceObject parent, String key, Object value) { DBTraceObject parent, String key, Object value) {
if (value instanceof AddressRange) { if (value instanceof AddressRange) {
DBTraceObjectAddressRangeValue entry = rangeValueMap DBTraceObjectAddressRangeValue entry = rangeValueMap
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null); .put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null);
entry.set(parent, key, false); entry.set(parent, key, false);
emitValueCreated(parent, entry);
return entry; return entry;
} }
else if (value instanceof Address) { else if (value instanceof Address) {
@ -272,15 +282,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
DBTraceObjectAddressRangeValue entry = rangeValueMap DBTraceObjectAddressRangeValue entry = rangeValueMap
.put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null); .put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null);
entry.set(parent, key, true); entry.set(parent, key, true);
emitValueCreated(parent, entry);
return entry; return entry;
} }
DBTraceObjectValue entry = valueStore.create(); DBTraceObjectValue entry = valueStore.create();
entry.set(lifespan, parent, key, value); entry.set(lifespan, parent, key, value);
if (parent != null) { emitValueCreated(parent, entry);
// Don't need event for root value created
parent.emitEvents(
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
}
return entry; return entry;
} }