diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java new file mode 100644 index 0000000000..8adcea7150 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java @@ -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 { + 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 getter; + private final BiConsumer setter; + private final Class cls; + + @SuppressWarnings("unchecked") + RegionTableColumns(String header, Class cls, Function getter, + BiConsumer setter) { + this.header = header; + this.cls = cls; + this.getter = getter; + this.setter = (BiConsumer) setter; + } + + RegionTableColumns(String header, Class cls, Function 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 regions = ctx.getSelectedRegions(); + if (regions.size() != 1) { + return null; + } + return regions.iterator().next(); + } + + protected static Set 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 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 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 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 sel) { + DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable, + regionTableModel, regionFilterPanel); + } + + public Collection 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); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java new file mode 100644 index 0000000000..a347700d68 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java @@ -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
{ + 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 ? "" + : ("" + + 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
{ + public RegionAddressColumn() { + super(Address.class); + } + + abstract Address fromRange(AddressRange range); + + @Override + public ValueProperty
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 { + public RegionLengthColumn() { + super(Long.class); + } + + @Override + public String getColumnName() { + return "Length"; + } + + @Override + public ValueProperty 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 ? "" + : ("0x" + + Long.toUnsignedString(value, 16)); + } + + @Override + public String getToolTip() { + return getDisplay(); + } + + @Override + public boolean isModified() { + return false; + } + }; + } + } + + public abstract static class RegionFlagColumn extends TraceValueObjectAttributeColumn { + 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 createTableColumnDescriptor() { + TableColumnDescriptor 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 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 seedPath = object.getCanonicalPath().getKeyList(); + List processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); + if (processPath != null) { + return successorRegions(rootSchema, processPath); + } + List 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 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 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 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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPlugin.java index 18efe8d07b..65330bf4b7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPlugin.java @@ -78,7 +78,7 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin { } else if (event instanceof TraceActivatedPluginEvent) { TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; - provider.setTrace(ev.getActiveCoordinates().getTrace()); + provider.coordinatesActivated(ev.getActiveCoordinates()); } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java index b3b512111f..77487d532a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java @@ -16,52 +16,58 @@ package ghidra.app.plugin.core.debug.gui.memory; import java.awt.BorderLayout; -import java.awt.event.*; +import java.awt.event.MouseEvent; import java.util.*; 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.table.TableColumn; -import javax.swing.table.TableColumnModel; + +import org.apache.commons.lang3.ArrayUtils; import docking.ActionContext; import docking.WindowPosition; import docking.action.*; import docking.action.builder.ActionBuilder; -import docking.widgets.table.CustomToStringCellRenderer; -import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; 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.model.DebuggerObjectActionContext; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider; 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.RegionMapProposal.RegionMapEntry; -import ghidra.framework.model.DomainObject; import ghidra.framework.plugintool.*; 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.mem.MemoryBlock; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.trace.model.*; -import ghidra.trace.model.Trace.TraceMemoryRegionChangeType; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.util.HelpLocation; import ghidra.util.Msg; -import ghidra.util.database.ObjectKey; -import ghidra.util.table.GhidraTable; -import ghidra.util.table.GhidraTableFilterPanel; 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 { String NAME = DebuggerResources.NAME_MAP_REGIONS; String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_REGIONS; @@ -108,122 +114,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { } } - protected enum RegionTableColumns - implements EnumeratedTableColumn { - 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 getter; - private final BiConsumer setter; - private final Class cls; - - @SuppressWarnings("unchecked") - RegionTableColumns(String header, Class cls, Function getter, - BiConsumer setter) { - this.header = header; - this.cls = cls; - this.getter = getter; - this.setter = (BiConsumer) setter; - } - - RegionTableColumns(String header, Class cls, Function 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 regions = ctx.getSelectedRegions(); - if (regions.size() != 1) { - return null; - } - return regions.iterator().next(); - } - - protected static Set 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 { public static final String GROUP = DebuggerResources.GROUP_GENERAL; @@ -241,7 +131,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { if (listingService == null) { return; } - Set regions = getSelectedRegions(myActionContext); + Set regions = getSelectedRegions(context); if (regions == null) { return; } @@ -255,42 +145,36 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { @Override public boolean isEnabledForContext(ActionContext context) { - Set sel = getSelectedRegions(myActionContext); + Set sel = getSelectedRegions(context); return sel != null && !sel.isEmpty(); } } - private final DebuggerRegionsPlugin plugin; + final DebuggerRegionsPlugin plugin; + @AutoServiceConsumed + ProgramManager programManager; + @AutoServiceConsumed + DebuggerListingService listingService; @AutoServiceConsumed private DebuggerStaticMappingService staticMappingService; @AutoServiceConsumed private DebuggerTraceManagerService traceManager; - @AutoServiceConsumed - private DebuggerListingService listingService; - @AutoServiceConsumed - ProgramManager programManager; @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 regionFilterPanel; + private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + private Program currentProgram; + private ProgramLocation currentLocation; private final JPanel mainPanel = new JPanel(new BorderLayout()); + DebuggerRegionsPanel panel; + DebuggerLegacyRegionsPanel legacyPanel; + // TODO: Lazy construction of these dialogs? private final DebuggerBlockChooserDialog blockChooserDialog; private final DebuggerRegionMapProposalDialog regionProposalDialog; - - private DebuggerRegionActionContext myActionContext; - private Program currentProgram; - private ProgramLocation currentLocation; - DockingAction actionMapRegions; DockingAction actionMapRegionTo; DockingAction actionMapRegionsTo; @@ -304,8 +188,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { DebuggerRegionActionContext.class); this.plugin = plugin; - regionTableModel = new RegionTableModel(tool); - setIcon(DebuggerResources.ICON_PROVIDER_REGIONS); setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS); setWindowMenuGroup(DebuggerPluginPackage.NAME); @@ -322,103 +204,28 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { createActions(); } - @Override - public ActionContext getActionContext(MouseEvent event) { - 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 static boolean isLegacy(Trace trace) { + return trace != null && trace.getObjectManager().getRootSchema() == null; } protected void buildMainPanel() { - regionTable = new GhidraTable(regionTableModel); - regionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - mainPanel.add(new JScrollPane(regionTable)); - 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); - } - } + panel = new DebuggerRegionsPanel(this); + mainPanel.add(panel); + legacyPanel = new DebuggerLegacyRegionsPanel(this); } protected void createActions() { actionMapRegions = MapRegionsAction.builder(plugin) - .withContext(DebuggerRegionActionContext.class) .enabledWhen(this::isContextNonEmpty) .popupWhen(this::isContextNonEmpty) .onAction(this::activatedMapRegions) .buildAndInstallLocal(this); actionMapRegionTo = MapRegionToAction.builder(plugin) - .withContext(DebuggerRegionActionContext.class) - .enabledWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1) - .popupWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1) + .enabledWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx)) + .popupWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx)) .onAction(this::activatedMapRegionTo) .buildAndInstallLocal(this); actionMapRegionsTo = MapRegionsToAction.builder(plugin) - .withContext(DebuggerRegionActionContext.class) .enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx)) .popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx)) .onAction(this::activatedMapRegionsTo) @@ -426,50 +233,76 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { actionSelectAddresses = new SelectAddressesAction(); actionSelectRows = SelectRowsAction.builder(plugin) .description("Select regions by trace selection") - .enabledWhen(ctx -> currentTrace != null) + .enabledWhen(ctx -> current.getTrace() != null) .onAction(this::activatedSelectCurrent) .buildAndInstallLocal(this); actionForceFullView = ForceFullViewAction.builder(plugin) - .enabledWhen(ctx -> currentTrace != null) + .enabledWhen(ctx -> current.getTrace() != null) .onAction(this::activatedForceFullView) .buildAndInstallLocal(this); contextChanged(); } - private boolean isContextNonEmpty(DebuggerRegionActionContext ctx) { - return !ctx.getSelectedRegions().isEmpty(); - } - - private static Set getSelectedRegions(DebuggerRegionActionContext ctx) { - if (ctx == null) { - return null; + @Override + public ActionContext getActionContext(MouseEvent event) { + final ActionContext context; + if (isLegacy(current.getTrace())) { + context = legacyPanel.getActionContext(); } - return ctx.getSelectedRegions() - .stream() - .map(r -> r.getRegion()) - .collect(Collectors.toSet()); - } - - private void activatedMapRegions(DebuggerRegionActionContext ignored) { - mapRegions(getSelectedRegions(myActionContext)); - } - - private void activatedMapRegionsTo(DebuggerRegionActionContext ignored) { - Set sel = getSelectedRegions(myActionContext); - if (sel == null || sel.isEmpty()) { - return; + else { + context = panel.getActionContext(); } - mapRegionsTo(sel); + if (context != null) { + return context; + } + return super.getActionContext(event); } - private void activatedMapRegionTo(DebuggerRegionActionContext ignored) { - Set sel = getSelectedRegions(myActionContext); + private boolean isContextNonEmpty(ActionContext context) { + 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 sel = getSelectedRegions(context); + return sel != null && sel.size() == 1; + } + + private static Set 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 sel = getSelectedRegions(context); if (sel == null || sel.size() != 1) { return; } mapRegionTo(sel.iterator().next()); } + private void activatedMapRegionsTo(ActionContext context) { + Set sel = getSelectedRegions(context); + if (sel == null || sel.isEmpty()) { + return; + } + mapRegionsTo(sel); + } + protected void promptRegionProposal(Collection proposal) { if (proposal.isEmpty()) { Msg.showInfo(this, getComponent(), "Map Regions", @@ -483,7 +316,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { return; } tool.executeBackgroundCommand( - new MapRegionsBackgroundCommand(staticMappingService, adjusted), currentTrace); + new MapRegionsBackgroundCommand(staticMappingService, adjusted), current.getTrace()); } protected void mapRegions(Set regions) { @@ -524,13 +357,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { } private void activatedSelectCurrent(ActionContext ignored) { - if (listingService == null || traceManager == null || currentTrace == null) { + if (listingService == null || traceManager == null || current.getTrace() == null) { return; } // TODO: Select from other listings? ProgramSelection progSel = listingService.getCurrentSelection(); - TraceMemoryManager memoryManager = currentTrace.getMemoryManager(); + TraceMemoryManager memoryManager = current.getTrace().getMemoryManager(); if (progSel != null && !progSel.isEmpty()) { Set regSel = new HashSet<>(); for (AddressRange range : progSel) { @@ -552,21 +385,22 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { } private void activatedForceFullView(ActionContext ignored) { - if (currentTrace == null) { + if (current.getTrace() == null) { return; } - currentTrace.getProgramView() + current.getTrace() + .getProgramView() .getMemory() .setForceFullView(actionForceFullView.isSelected()); } public void setSelectedRegions(Set sel) { - DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable, - regionTableModel, regionFilterPanel); - } - - public Collection getSelectedRows() { - return regionFilterPanel.getSelectedItems(); + if (isLegacy(current.getTrace())) { + legacyPanel.setSelectedRegions(sel); + } + else { + panel.setSelectedRegions(sel); + } } @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 public void contextChanged() { super.contextChanged(); - if (currentTrace != null) { - actionForceFullView.setSelected(currentTrace.getProgramView() + if (current.getTrace() != null) { + actionForceFullView.setSelected(current.getTrace() + .getProgramView() .getMemory() .isForceFullView()); } } - private void removeOldListeners() { - if (currentTrace == null) { - return; - } - currentTrace.removeListener(regionsListener); - } - - private void addNewListeners() { - if (currentTrace == null) { - return; - } - currentTrace.addListener(regionsListener); - } - public Entry askBlock(TraceMemoryRegion region, Program program, MemoryBlock block) { if (programManager == null) { @@ -646,4 +456,32 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { return blockChooserDialog.chooseBlock(getTool(), region, 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(); + } + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java index 315da4bf89..baabc988a6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java @@ -21,6 +21,7 @@ import java.util.stream.Stream; import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener; import docking.widgets.table.threaded.ThreadedTableModel; +import ghidra.framework.model.DomainObjectChangeRecord; import ghidra.framework.plugintool.Plugin; import ghidra.trace.model.*; import ghidra.trace.model.Trace.TraceObjectChangeType; @@ -36,6 +37,7 @@ public abstract class AbstractQueryTableModel extends ThreadedTableModel extends ThreadedTableModel> extends JPanel { + public interface CellActivationListener { + void cellActivated(JTable table); + } + protected final M tableModel; protected final GhidraTable table; protected final GhidraTableFilterPanel filterPanel; @@ -46,6 +50,9 @@ public abstract class AbstractQueryTablePanel cellActivationListeners = + new ListenerSet<>(CellActivationListener.class); + public AbstractQueryTablePanel(Plugin plugin) { super(new BorderLayout()); tableModel = createModel(plugin); @@ -54,6 +61,24 @@ public abstract class AbstractQueryTablePanel 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) { T t = tableModel.findTraceObject(object); if (t == null) { @@ -179,6 +189,15 @@ public abstract class AbstractQueryTablePanel objects) { + List ts = objects.stream().map(tableModel::findTraceObject).collect(Collectors.toList()); + if (ts.isEmpty()) { + return false; + } + setSelectedItems(ts); + return true; + } + public List getSelectedItems() { return filterPanel.getSelectedItems(); } @@ -192,7 +211,8 @@ public abstract class AbstractQueryTablePanel DynamicTableColumn getColumnByNameAndType(String name, Class type) { + public Map.Entry> getColumnByNameAndType( + String name, Class type) { int count = tableModel.getColumnCount(); for (int i = 0; i < count; i++) { DynamicTableColumn column = tableModel.getColumn(i); @@ -202,7 +222,8 @@ public abstract class AbstractQueryTablePanel) column; + return Map.entry(table.convertColumnIndexToView(i), + (DynamicTableColumn) column); } return null; } @@ -214,4 +235,8 @@ public abstract class AbstractQueryTablePanel { + private final CellActivationListener elementActivationListener = + table -> activatedElementsTable(); + private final CellActivationListener attributeActivationListener = + table -> activatedAttributesTable(); + + private final SeekListener seekListener = pos -> { long snap = Math.round(pos); if (current.getTrace() == null || snap < 0) { snap = 0; @@ -320,44 +323,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable } }); - elementsTablePanel.addMouseListener(new MouseAdapter() { - @Override - 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.addCellActivationListener(elementActivationListener); + attributesTablePanel.addCellActivationListener(attributeActivationListener); elementsTablePanel.addSeekListener(seekListener); attributesTablePanel.addSeekListener(seekListener); @@ -393,16 +360,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable .enabledWhen(this::hasSingleLink) .onAction(this::activatedFollowLink) .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() { @@ -480,44 +437,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable 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 public JComponent getComponent() { return mainPanel; @@ -533,7 +452,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable return null; } 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) { return null; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java index 2efa069b55..c84611bc1d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTableModel.java @@ -49,6 +49,86 @@ public class ObjectTableModel extends AbstractQueryTableModel { }); } + public interface ValueProperty { + public Class getType(); + + public ValueRow getRow(); + + public T getValue(); + + public String getDisplay(); + + public String getHtmlDisplay(); + + public String getToolTip(); + + public boolean isModified(); + } + + public static abstract class ValueDerivedProperty implements ValueProperty { + protected final ValueRow row; + protected final Class type; + + public ValueDerivedProperty(ValueRow row, Class type) { + this.row = row; + this.type = type; + } + + @Override + public ValueRow getRow() { + return row; + } + + @Override + public Class getType() { + return type; + } + } + + public record ValueAttribute (ValueRow row, String name, Class type) + implements ValueProperty { + public TraceObjectValue getEntry() { + return row.getAttributeEntry(name); + } + + @Override + public ValueRow getRow() { + return row; + } + + @Override + public Class 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 { String getKey(); @@ -79,7 +159,11 @@ public class ObjectTableModel extends AbstractQueryTableModel { */ boolean isModified(); - TraceObjectValue getAttribute(String attributeName); + default ValueAttribute getAttribute(String attributeName, Class type) { + return new ValueAttribute<>(this, attributeName, type); + } + + TraceObjectValue getAttributeEntry(String attributeName); String getAttributeDisplay(String attributeName); @@ -143,7 +227,7 @@ public class ObjectTableModel extends AbstractQueryTableModel { } @Override - public TraceObjectValue getAttribute(String attributeName) { + public TraceObjectValue getAttributeEntry(String attributeName) { return null; } @@ -196,28 +280,28 @@ public class ObjectTableModel extends AbstractQueryTableModel { } @Override - public TraceObjectValue getAttribute(String attributeName) { + public TraceObjectValue getAttributeEntry(String attributeName) { return object.getAttribute(getSnap(), attributeName); } @Override public String getAttributeDisplay(String attributeName) { - return display.getEdgeDisplay(getAttribute(attributeName)); + return display.getEdgeDisplay(getAttributeEntry(attributeName)); } @Override public String getAttributeHtmlDisplay(String attributeName) { - return display.getEdgeHtmlDisplay(getAttribute(attributeName)); + return display.getEdgeHtmlDisplay(getAttributeEntry(attributeName)); } @Override public String getAttributeToolTip(String attributeName) { - return display.getEdgeToolTip(getAttribute(attributeName)); + return display.getEdgeToolTip(getAttributeEntry(attributeName)); } @Override public boolean isAttributeModified(String attributeName) { - return isValueModified(getAttribute(attributeName)); + return isValueModified(getAttributeEntry(attributeName)); } } @@ -269,21 +353,21 @@ public class ObjectTableModel extends AbstractQueryTableModel { } } - static class AutoAttributeColumn extends TraceValueObjectAttributeColumn { - public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx, + static class AutoAttributeColumn extends TraceValueObjectAttributeColumn { + public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) { String name = attributeSchema.getName(); 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 attributeType) { super(attributeName, attributeType); } } // TODO: Save and restore these between sessions, esp., their settings - private Map columnCache = new HashMap<>(); + private Map> columnCache = new HashMap<>(); protected ObjectTableModel(Plugin plugin) { super("Object Model", plugin); @@ -454,14 +538,14 @@ public class ObjectTableModel extends AbstractQueryTableModel { int count = getColumnCount(); for (int i = 0; i < count; i++) { DynamicTableColumn column = getColumn(i); - if (column instanceof TraceValueObjectAttributeColumn attrCol) { + if (column instanceof TraceValueObjectAttributeColumn attrCol) { attrCol.setDiffColor(diffColor); } else if (column instanceof TraceValueValColumn valCol) { valCol.setDiffColor(diffColor); } } - for (TraceValueObjectAttributeColumn column : columnCache.values()) { + for (TraceValueObjectAttributeColumn column : columnCache.values()) { column.setDiffColor(diffColor); } } @@ -471,14 +555,14 @@ public class ObjectTableModel extends AbstractQueryTableModel { int count = getColumnCount(); for (int i = 0; i < count; i++) { DynamicTableColumn column = getColumn(i); - if (column instanceof TraceValueObjectAttributeColumn attrCol) { + if (column instanceof TraceValueObjectAttributeColumn attrCol) { attrCol.setDiffColorSel(diffColorSel); } else if (column instanceof TraceValueValColumn valCol) { valCol.setDiffColorSel(diffColorSel); } } - for (TraceValueObjectAttributeColumn column : columnCache.values()) { + for (TraceValueObjectAttributeColumn column : columnCache.values()) { column.setDiffColorSel(diffColorSel); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java index d2db03f715..710d46c496 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifePlotColumn.java @@ -27,7 +27,7 @@ import ghidra.trace.model.Trace; import ghidra.util.table.column.GColumnRenderer; public class TraceValueLifePlotColumn - extends AbstractDynamicTableColumn, Trace> { + extends AbstractDynamicTableColumn, Trace> { private final SpanSetTableCellRenderer cellRenderer = new SpanSetTableCellRenderer<>(); private final RangeCursorTableHeaderRenderer headerRenderer = @@ -45,7 +45,7 @@ public class TraceValueLifePlotColumn } @Override - public GColumnRenderer> getColumnRenderer() { + public GColumnRenderer> getColumnRenderer() { return cellRenderer; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectAttributeColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectAttributeColumn.java index ec7bb3015a..43560c858f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectAttributeColumn.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectAttributeColumn.java @@ -15,18 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.model.columns; -import java.awt.Color; -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.ValueProperty; import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow; import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet; 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.TargetObjectSchema; 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.TraceObjectValue; -import ghidra.util.table.column.AbstractGColumnRenderer; -import ghidra.util.table.column.GColumnRenderer; -public class TraceValueObjectAttributeColumn - extends AbstractDynamicTableColumn { - - public class AttributeRenderer extends AbstractGColumnRenderer - 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 class TraceValueObjectAttributeColumn + extends TraceValueObjectPropertyColumn { public static Class computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) { TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema()); @@ -105,18 +55,11 @@ public class TraceValueObjectAttributeColumn return type; } - private final String attributeName; - private final Class attributeType; - private final AttributeRenderer renderer = new AttributeRenderer(); - private final Comparator comparator; + protected final String attributeName; - private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED; - private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL; - - public TraceValueObjectAttributeColumn(String attributeName, Class attributeType) { + public TraceValueObjectAttributeColumn(String attributeName, Class attributeType) { + super(attributeType); this.attributeName = attributeName; - this.attributeType = attributeType; - this.comparator = newTypedComparator(); } @Override @@ -131,43 +74,7 @@ public class TraceValueObjectAttributeColumn } @Override - public ValueRow getValue(ValueRow rowObject, Settings settings, Trace data, - ServiceProvider serviceProvider) throws IllegalArgumentException { - return rowObject; - } - - @Override - public GColumnRenderer getColumnRenderer() { - return renderer; - } - - @Override - public Comparator 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 > Comparator newTypedComparator() { - if (Comparable.class.isAssignableFrom(attributeType)) { - @SuppressWarnings("unchecked") - Class cls = (Class) attributeType.asSubclass(Comparable.class); - Function 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; + public ValueProperty getProperty(ValueRow row) { + return row.getAttribute(attributeName, propertyType); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectPropertyColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectPropertyColumn.java new file mode 100644 index 0000000000..689b7e5224 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueObjectPropertyColumn.java @@ -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 + extends AbstractDynamicTableColumn, Trace> { + + public class PropertyRenderer extends AbstractGColumnRenderer> + implements ColorsModified.InTable { + { + setHTMLRenderingEnabled(true); + } + + @Override + public String getFilterString(ValueProperty p, Settings settings) { + return p.getDisplay(); + } + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + super.getTableCellRendererComponent(data); + @SuppressWarnings("unchecked") + ValueProperty p = (ValueProperty) 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 property = (ValueProperty) 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 propertyType; + private final GColumnRenderer> renderer; + private final Comparator> comparator; + + private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED; + private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL; + + public TraceValueObjectPropertyColumn(Class propertyType) { + this.propertyType = propertyType; + this.comparator = newTypedComparator(); + this.renderer = createRenderer(); + } + + public GColumnRenderer> createRenderer() { + if (propertyType == Boolean.class) { + return new BooleanPropertyRenderer(); + } + return new PropertyRenderer(); + } + + @Override + public GColumnRenderer> getColumnRenderer() { + return renderer; + } + + @Override + public Comparator> getComparator(DynamicColumnTableModel model, + int columnIndex) { + return comparator == null ? null + : comparator.thenComparing( + new ColumnRenderedValueBackupComparator<>(model, columnIndex)); + } + + public abstract ValueProperty getProperty(ValueRow row); + + @Override + public ValueProperty getValue(ValueRow rowObject, Settings settings, Trace data, + ServiceProvider serviceProvider) throws IllegalArgumentException { + return getProperty(rowObject); + } + + protected > Comparator> newTypedComparator() { + if (Comparable.class.isAssignableFrom(propertyType)) { + @SuppressWarnings("unchecked") + Class cls = (Class) propertyType.asSubclass(Comparable.class); + Function, 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; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java index 404b4f9f71..da032d24d8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPanel.java @@ -18,17 +18,20 @@ package ghidra.app.plugin.core.debug.gui.stack; import java.util.List; import java.util.stream.Collectors; +import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import docking.ActionContext; import docking.widgets.table.AbstractDynamicTableColumn; 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.ValueProperty; 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.TraceValueObjectAttributeColumn; +import ghidra.app.services.DebuggerListingService; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.dbg.target.TargetStack; 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.listing.Function; 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.Suppression; -public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelectionListener { +public class DebuggerStackPanel extends ObjectsTablePanel + implements ListSelectionListener, CellActivationListener { private static class FrameLevelColumn extends TraceValueKeyColumn { @Override @@ -58,7 +63,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti } } - private static class FramePcColumn extends TraceValueObjectAttributeColumn { + private static class FramePcColumn extends TraceValueObjectAttributeColumn
{ public FramePcColumn() { super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class); } @@ -80,7 +85,8 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti @Override public Function getValue(ValueRow rowObject, Settings settings, Trace data, 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()); } } @@ -96,13 +102,14 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti descriptor.addVisibleColumn(new FrameLevelColumn()); descriptor.addVisibleColumn(new FramePcColumn()); descriptor.addVisibleColumn(new FrameFunctionColumn()); - // TODO: Comment column? return descriptor; } } private final DebuggerStackProvider provider; + @AutoServiceConsumed + protected DebuggerListingService listingService; @AutoServiceConsumed protected DebuggerTraceManagerService traceManager; @SuppressWarnings("unused") @@ -122,6 +129,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti setShowHidden(false); addSelectionListener(this); + addCellActivationListener(this); } @Override @@ -129,7 +137,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti return new StackTableModel(plugin); } - public ActionContext getActionContext() { + public DebuggerObjectActionContext getActionContext() { return myActionContext; } @@ -137,8 +145,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti if (object == null) { return ModelQuery.EMPTY; } - TargetObjectSchema rootSchema = object.getRoot() - .getTargetSchema(); + TargetObjectSchema rootSchema = object.getRoot().getTargetSchema(); List stackPath = rootSchema .searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList()); if (stackPath == null) { @@ -176,4 +183,21 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti 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); + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPlugin.java index 08a6858ad8..3708a78c0c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackPlugin.java @@ -22,11 +22,6 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.framework.plugintool.*; 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( shortDescription = "Debugger stack view", diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java index ccfda07617..65a0547385 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java @@ -110,10 +110,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { @Override public ActionContext getActionContext(MouseEvent event) { + final ActionContext context; if (isLegacy(current.getTrace())) { - return legacyPanel.getActionContext(); + context = legacyPanel.getActionContext(); + } + else { + context = panel.getActionContext(); } - ActionContext context = panel.getActionContext(); if (context != null) { return context; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingProposals.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingProposals.java index 32d69c345a..fb9a3ded64 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingProposals.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingProposals.java @@ -255,7 +255,7 @@ public enum DebuggerStaticMappingProposals { protected static Set getLikelyModulesFromName(TraceMemoryRegion region) { String key; try { - List path = PathUtils.parse(region.getName()); + List path = PathUtils.parse(region.getPath()); key = PathUtils.getKey(path); if (PathUtils.isIndex(key)) { key = PathUtils.parseIndex(key); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index 41b0d16c59..5a876a4208 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -40,11 +40,14 @@ import org.junit.runner.Description; import docking.ActionContext; import docking.action.ActionContextProvider; import docking.action.DockingActionIf; +import docking.widgets.table.DynamicTableColumn; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import generic.Unique; import ghidra.app.nav.Navigatable; 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.service.model.*; 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.target.*; import ghidra.dbg.testutil.DebuggerModelTestUtils; +import ghidra.docking.settings.SettingsImpl; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramDB; @@ -486,6 +490,19 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest runSwing(() -> nav.setSelection(sel)); } + protected Object rowColVal(ValueRow row, DynamicTableColumn 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 String rowColDisplay(ValueRow row, DynamicTableColumn 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) { return LocationTrackingSpecFactory.fromConfigName(name); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java new file mode 100644 index 0000000000..925e130299 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java @@ -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 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 mappings = + tb.trace.getStaticMappingManager().getAllEntries(); + assertEquals(0, mappings.size()); + + pressButtonByText(propDialog, "OK", true); + waitForDomainObject(tb.trace); + assertEquals(2, mappings.size()); + Iterator 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()))); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java deleted file mode 100644 index 3eadcbce53..0000000000 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java +++ /dev/null @@ -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("" + // - "" + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - ""); - - try (UndoableTransaction tid = tb.startTransaction()) { - tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); - } - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java index 1dc00a4e41..9b263f462c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java @@ -17,61 +17,118 @@ package ghidra.app.plugin.core.debug.gui.memory; import static org.junit.Assert.*; +import java.io.IOException; import java.util.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.junit.experimental.categories.Category; -import generic.Unique; +import docking.widgets.table.DynamicTableColumn; 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.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.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.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.MemoryBlock; import ghidra.program.util.ProgramSelection; 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.target.*; +import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.util.database.UndoableTransaction; +import ghidra.util.table.GhidraTable; @Category(NightlyCategory.class) public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest { DebuggerRegionsProvider provider; - protected TraceMemoryRegion regionExeText; - protected TraceMemoryRegion regionExeData; - protected TraceMemoryRegion regionLibText; - protected TraceMemoryRegion regionLibData; + TraceObjectMemoryRegion regionExeText; + TraceObjectMemoryRegion regionExeData; + TraceObjectMemoryRegion regionLibText; + TraceObjectMemoryRegion regionLibData; - protected MemoryBlock blockExeText; - protected MemoryBlock blockExeData; + MemoryBlock blockExeText; + MemoryBlock blockExeData; - @Before - public void setUpRegionsTest() throws Exception { - addPlugin(tool, DebuggerRegionsPlugin.class); - provider = waitForComponentProvider(DebuggerRegionsProvider.class); + 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(""" + + + + + + + + + + + + """); + + 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 { - 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); + regionExeText = addRegion("/bin/echo .text", 0, tb.range(0x55550000, 0x555500ff)); + regionExeData = addRegion("/bin/echo .data", 0, tb.range(0x55750000, 0x5575007f)); + regionLibText = addRegion("/lib/libc.so .text", 0, tb.range(0x7f000000, 0x7f0003ff)); + regionLibData = addRegion("/lib/libc.so .data", 0, tb.range(0x7f100000, 0x7f10003f)); } } @@ -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 nameCol = + provider.panel.getColumnByNameAndType("Name", ValueRow.class).getValue(); + DynamicTableColumn startCol = + provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getValue(); + DynamicTableColumn endCol = + provider.panel.getColumnByNameAndType("End", ValueProperty.class).getValue(); + DynamicTableColumn lengthCol = + provider.panel.getColumnByNameAndType("Length", ValueProperty.class).getValue(); + DynamicTableColumn readCol = + provider.panel.getColumnByNameAndType("Read", ValueProperty.class).getValue(); + DynamicTableColumn writeCol = + provider.panel.getColumnByNameAndType("Write", ValueProperty.class).getValue(); + DynamicTableColumn 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 public void testNoTraceEmpty() throws Exception { - assertEquals(0, provider.regionTableModel.getModelData().size()); + waitForPass(() -> assertTableSize(0)); } @Test public void testActivateEmptyTraceEmpty() throws Exception { createAndOpenTrace(); traceManager.activateTrace(tb.trace); - waitForSwing(); + waitForTasks(); - assertEquals(0, provider.regionTableModel.getModelData().size()); + waitForPass(() -> assertTableSize(0)); } @Test public void testAddThenActivateTracePopulates() throws Exception { - createTrace(); + createAndOpenTrace(); - TraceMemoryRegion region; + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); - waitForSwing(); + waitForTasks(); - RegionRow row = Unique.assertOne(provider.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()); + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff), + 0x10000, "rx"); + }); } @Test @@ -131,65 +226,81 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI createAndOpenTrace(); traceManager.activateTrace(tb.trace); - TraceMemoryRegion region; + waitForPass(() -> assertTableSize(0)); + + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } + waitForTasks(); - waitForSwing(); - - RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); - assertEquals(region, row.getRegion()); + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff), + 0x10000, "rx"); + }); } @Test - public void testDeleteRemoves() throws Exception { - createTrace(); + public void testRemoveRegionRemovesFromTable() throws Exception { + createAndOpenTrace(); - TraceMemoryRegion region; + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); - waitForSwing(); + waitForTasks(); - RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); - assertEquals(region, row.getRegion()); + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff), + 0x10000, "rx"); + }); try (UndoableTransaction tid = tb.startTransaction()) { - region.delete(); + region.getObject().removeTree(Lifespan.nowOn(0)); } waitForDomainObject(tb.trace); + waitForTasks(); - assertEquals(0, provider.regionTableModel.getModelData().size()); + waitForPass(() -> assertTableSize(0)); } @Test public void testUndoRedo() throws Exception { - createTrace(); + createAndOpenTrace(); + TraceObjectMemoryRegion region; 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); - waitForSwing(); - Unique.assertOne(provider.regionTableModel.getModelData()); + waitForTasks(); + + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff), + 0x10000, "rx"); + }); undo(tb.trace); - assertEquals(0, provider.regionTableModel.getModelData().size()); + waitForDomainObject(tb.trace); + waitForTasks(); + + waitForPass(() -> assertTableSize(0)); 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 @@ -197,17 +308,23 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI createAndOpenTrace(); traceManager.activateTrace(tb.trace); + TraceObjectMemoryRegion region; 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); - + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); 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(); } waitForDomainObject(tb.trace); - assertEquals(0, provider.regionTableModel.getModelData().size()); + waitForTasks(); + + waitForPass(() -> assertTableSize(0)); } @Test @@ -215,27 +332,31 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI addPlugin(tool, DebuggerListingPlugin.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); - createTrace(); + createAndOpenTrace(); - TraceMemoryRegion region; + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); - waitForSwing(); - waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); + waitForTasks(); + 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()); - assertEquals(region, row.getRegion()); + int startColIdx = + 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())); - clickTableCell(provider.regionTable, 0, RegionTableColumns.END.ordinal(), 2); + clickTableCell(table, 0, endColIdx, 2); waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress())); } @@ -260,15 +381,13 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI program.setName("echo"); } waitForDomainObject(program); - waitForPass(() -> assertEquals(4, provider.regionTable.getRowCount())); + waitForPass(() -> assertTableSize(4)); // 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(); - assertTrue(provider.actionMapRegions.isEnabled()); - - performAction(provider.actionMapRegions, false); + performEnabledAction(provider, provider.actionMapRegions, false); DebuggerRegionMapProposalDialog propDialog = waitForDialogComponent(DebuggerRegionMapProposalDialog.class); @@ -277,16 +396,16 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(2, proposal.size()); RegionMapEntry entry; - // Table sorts by name by default. - // Names are file name followed by min address, so .text is first. + // Table sorts by name by default, so .data 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()); + 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 = waitForDialogComponent(DebuggerBlockChooserDialog.class); @@ -294,7 +413,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(blockExeText, row.getBlock()); pressButtonByText(blockDialog, "OK", true); - assertEquals(blockExeData, entry.getBlock()); // Unchanged + assertEquals(blockExeText, entry.getBlock()); // Unchanged // TODO: Test the changed case Collection mappings = @@ -304,54 +423,50 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI pressButtonByText(propDialog, "OK", true); waitForDomainObject(tb.trace); assertEquals(2, mappings.size()); + // Ordered by db key. Thus, in order added Iterator 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()); + 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()); } - // TODO: testActionMapRegionsTo - // TODO: testActionMapRegionTo - @Test public void testActionSelectAddresses() throws Exception { addPlugin(tool, DebuggerListingPlugin.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); - createTrace(); + createAndOpenTrace(); - TraceMemoryRegion region; + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); - waitForSwing(); + waitForTasks(); - RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); - waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); - assertEquals(region, row.getRegion()); - assertFalse(tb.trace.getProgramView().getMemory().isEmpty()); + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), + tb.addr(0x0040ffff), 0x10000, "rx"); + }); + waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty())); provider.setSelectedRegions(Set.of(region)); waitForSwing(); - assertTrue(provider.actionSelectAddresses.isEnabled()); - performAction(provider.actionSelectAddresses); + performEnabledAction(provider, provider.actionSelectAddresses, true); waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)), new AddressSet(listing.getSelection()))); @@ -362,33 +477,33 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI addPlugin(tool, DebuggerListingPlugin.class); DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class); - createTrace(); + createAndOpenTrace(); - TraceMemoryRegion region; + TraceObjectMemoryRegion 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); + region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff)); } - traceManager.openTrace(tb.trace); traceManager.activateTrace(tb.trace); waitForSwing(); - RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); - // NB. Table is debounced - waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount())); - assertEquals(region, row.getRegion()); - assertFalse(tb.trace.getProgramView().getMemory().isEmpty()); + waitForPass(() -> { + assertTableSize(1); + assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), + tb.addr(0x0040ffff), 0x10000, "rx"); + }); + waitForPass(() -> 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(); - assertTrue(provider.actionSelectRows.isEnabled()); - performAction(provider.actionSelectRows); + performEnabledAction(listing, provider.actionSelectRows, true); - waitForPass(() -> assertEquals(Set.of(row), Set.copyOf(provider.getSelectedRows()))); + waitForPass(() -> { + List allItems = provider.panel.getAllItems(); + assertEquals(Set.of(allItems.get(0)), Set.copyOf(provider.panel.getSelectedItems())); + }); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/QueryPanelTestHelper.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/QueryPanelTestHelper.java new file mode 100644 index 0000000000..11c40c4859 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/QueryPanelTestHelper.java @@ -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; + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java index 27cc818af5..086d3a851b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java @@ -27,8 +27,8 @@ import org.junit.*; import docking.widgets.table.DynamicTableColumn; import ghidra.app.plugin.core.debug.DebuggerCoordinates; 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.columns.TraceValueObjectAttributeColumn; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; 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.util.PathPattern; import ghidra.dbg.util.PathUtils; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; import ghidra.program.model.lang.Register; @@ -214,23 +213,15 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe assertEquals(size, stackProvider.panel.getAllItems().size()); } - protected Object rowColVal(ValueRow row, DynamicTableColumn 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) { ValueRow row = stackProvider.panel.getAllItems().get(level); DynamicTableColumn levelCol = - stackProvider.panel.getColumnByNameAndType("Level", String.class); - DynamicTableColumn pcCol = - stackProvider.panel.getColumnByNameAndType("PC", ValueRow.class); + stackProvider.panel.getColumnByNameAndType("Level", String.class).getValue(); + DynamicTableColumn pcCol = + stackProvider.panel.getColumnByNameAndType("PC", ValueProperty.class).getValue(); DynamicTableColumn funcCol = - stackProvider.panel.getColumnByNameAndType("Function", Function.class); + stackProvider.panel.getColumnByNameAndType("Function", Function.class).getValue(); assertEquals(PathUtils.makeKey(PathUtils.makeIndex(level)), rowColVal(row, levelCol)); assertEquals(pcVal, rowColVal(row, pcCol)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java index 9bc7cdd1e9..ebb157a12f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java @@ -356,8 +356,8 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra DBTrace trace = object.getTrace(); switch (key) { case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME: - trace.updateViewsChangeRegionBlockRange(this, - (AddressRange) oldValue, (AddressRange) newValue); + // NB. old/newValue are null here. The CREATED event just has the new entry. + trace.updateViewsRefreshBlocks(); return; case TargetObject.DISPLAY_ATTRIBUTE_NAME: trace.updateViewsChangeRegionBlockName(this); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java index 2edbdcc4c4..ec8e008f5c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java @@ -44,7 +44,7 @@ public abstract class AbstractDBTraceProgramViewMemory protected final DBTraceProgramView program; protected final DBTraceMemoryManager memoryManager; - protected AddressSetView addressSet; + protected volatile AddressSetView addressSet; protected boolean forceFullView = false; protected long snap; @@ -79,7 +79,7 @@ public abstract class AbstractDBTraceProgramViewMemory } } - protected void computeFullAdddressSet() { + protected synchronized void computeFullAdddressSet() { AddressSet temp = new AddressSet(); forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress())); addressSet = temp; @@ -125,17 +125,17 @@ public abstract class AbstractDBTraceProgramViewMemory } @Override - public synchronized AddressSetView getLoadedAndInitializedAddressSet() { + public AddressSetView getLoadedAndInitializedAddressSet() { return addressSet; } @Override - public synchronized AddressSetView getAllInitializedAddressSet() { + public AddressSetView getAllInitializedAddressSet() { return addressSet; } @Override - public synchronized AddressSetView getInitializedAddressSet() { + public AddressSetView getInitializedAddressSet() { return addressSet; } @@ -230,7 +230,7 @@ public abstract class AbstractDBTraceProgramViewMemory } @Override - public synchronized long getSize() { + public long getSize() { return addressSet.getNumAddresses(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java index 582bce05b0..cd73cf72be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java @@ -74,11 +74,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { } @Override - protected void recomputeAddressSet() { + protected synchronized void recomputeAddressSet() { AddressSet temp = new AddressSet(); // TODO: Performance test this forVisibleRegions(reg -> temp.add(reg.getRange())); addressSet = temp; + System.err.println("Recomputed: " + temp); } protected MemoryBlock getRegionBlock(TraceMemoryRegion region) { @@ -139,7 +140,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange, AddressRange newRange) { - // TODO: update cached block? Nothing to update. changeRange(oldRange, newRange); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index ed7a7bae8a..9bf9acbf8b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -258,12 +258,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager 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, DBTraceObject parent, String key, Object value) { if (value instanceof AddressRange) { DBTraceObjectAddressRangeValue entry = rangeValueMap .put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null); entry.set(parent, key, false); + emitValueCreated(parent, entry); return entry; } else if (value instanceof Address) { @@ -272,15 +282,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager DBTraceObjectAddressRangeValue entry = rangeValueMap .put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null); entry.set(parent, key, true); + emitValueCreated(parent, entry); return entry; } DBTraceObjectValue entry = valueStore.create(); entry.set(lifespan, parent, key, value); - if (parent != null) { - // Don't need event for root value created - parent.emitEvents( - new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry)); - } + emitValueCreated(parent, entry); return entry; }