mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2765: Re-factor RegionsProvider for object-based trace
This commit is contained in:
parent
da766b7e69
commit
3327cc6bb8
25 changed files with 1962 additions and 794 deletions
|
@ -0,0 +1,342 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.widgets.table.CustomToStringCellRenderer;
|
||||||
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||||
|
import ghidra.app.services.DebuggerListingService;
|
||||||
|
import ghidra.framework.model.DomainObject;
|
||||||
|
import ghidra.framework.plugintool.AutoService;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
|
import ghidra.util.database.ObjectKey;
|
||||||
|
import ghidra.util.table.GhidraTable;
|
||||||
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
|
public class DebuggerLegacyRegionsPanel extends JPanel {
|
||||||
|
|
||||||
|
protected enum RegionTableColumns
|
||||||
|
implements EnumeratedTableColumn<RegionTableColumns, RegionRow> {
|
||||||
|
NAME("Name", String.class, RegionRow::getName, RegionRow::setName),
|
||||||
|
LIFESPAN("Lifespan", Lifespan.class, RegionRow::getLifespan),
|
||||||
|
START("Start", Address.class, RegionRow::getMinAddress),
|
||||||
|
END("End", Address.class, RegionRow::getMaxAddress),
|
||||||
|
LENGTH("Length", Long.class, RegionRow::getLength),
|
||||||
|
READ("Read", Boolean.class, RegionRow::isRead, RegionRow::setRead),
|
||||||
|
WRITE("Write", Boolean.class, RegionRow::isWrite, RegionRow::setWrite),
|
||||||
|
EXECUTE("Execute", Boolean.class, RegionRow::isExecute, RegionRow::setExecute),
|
||||||
|
VOLATILE("Volatile", Boolean.class, RegionRow::isVolatile, RegionRow::setVolatile);
|
||||||
|
|
||||||
|
private final String header;
|
||||||
|
private final Function<RegionRow, ?> getter;
|
||||||
|
private final BiConsumer<RegionRow, Object> setter;
|
||||||
|
private final Class<?> cls;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter,
|
||||||
|
BiConsumer<RegionRow, T> setter) {
|
||||||
|
this.header = header;
|
||||||
|
this.cls = cls;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = (BiConsumer<RegionRow, Object>) setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter) {
|
||||||
|
this(header, cls, getter, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getValueClass() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable(RegionRow row) {
|
||||||
|
return setter != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueOf(RegionRow row, Object value) {
|
||||||
|
setter.accept(row, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueOf(RegionRow row) {
|
||||||
|
return getter.apply(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class RegionTableModel
|
||||||
|
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||||
|
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
|
||||||
|
|
||||||
|
public RegionTableModel(PluginTool tool) {
|
||||||
|
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
|
||||||
|
RegionRow::new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static RegionRow getSelectedRegionRow(ActionContext context) {
|
||||||
|
if (!(context instanceof DebuggerRegionActionContext)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
|
||||||
|
Set<RegionRow> regions = ctx.getSelectedRegions();
|
||||||
|
if (regions.size() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return regions.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
|
||||||
|
if (!(context instanceof DebuggerRegionActionContext)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
|
||||||
|
return ctx.getSelectedRegions()
|
||||||
|
.stream()
|
||||||
|
.map(r -> r.getRegion())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegionsListener extends TraceDomainObjectListener {
|
||||||
|
public RegionsListener() {
|
||||||
|
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||||
|
|
||||||
|
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionAdded);
|
||||||
|
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
|
||||||
|
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
|
||||||
|
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void objectRestored() {
|
||||||
|
loadRegions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void regionAdded(TraceMemoryRegion region) {
|
||||||
|
regionTableModel.addItem(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void regionChanged(TraceMemoryRegion region) {
|
||||||
|
regionTableModel.updateItem(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void regionDeleted(TraceMemoryRegion region) {
|
||||||
|
regionTableModel.deleteItem(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void activatedSelectAddresses(DebuggerRegionActionContext ctx) {
|
||||||
|
if (listingService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<TraceMemoryRegion> regions = getSelectedRegions(ctx);
|
||||||
|
if (regions == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddressSet sel = new AddressSet();
|
||||||
|
for (TraceMemoryRegion s : regions) {
|
||||||
|
sel.add(s.getRange());
|
||||||
|
}
|
||||||
|
ProgramSelection ps = new ProgramSelection(sel);
|
||||||
|
listingService.setCurrentSelection(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerListingService listingService;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
private Trace currentTrace;
|
||||||
|
|
||||||
|
private final RegionsListener regionsListener = new RegionsListener();
|
||||||
|
|
||||||
|
protected final RegionTableModel regionTableModel;
|
||||||
|
protected GhidraTable regionTable;
|
||||||
|
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
|
||||||
|
|
||||||
|
private DebuggerRegionActionContext myActionContext;
|
||||||
|
|
||||||
|
public DebuggerLegacyRegionsPanel(DebuggerRegionsProvider provider) {
|
||||||
|
super(new BorderLayout());
|
||||||
|
this.provider = provider;
|
||||||
|
this.autoServiceWiring = AutoService.wireServicesConsumed(provider.plugin, this);
|
||||||
|
|
||||||
|
regionTableModel = new RegionTableModel(provider.getTool());
|
||||||
|
|
||||||
|
regionTable = new GhidraTable(regionTableModel);
|
||||||
|
regionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
|
add(new JScrollPane(regionTable));
|
||||||
|
regionFilterPanel = new GhidraTableFilterPanel<>(regionTable, regionTableModel);
|
||||||
|
add(regionFilterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
regionTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||||
|
myActionContext = new DebuggerRegionActionContext(provider,
|
||||||
|
regionFilterPanel.getSelectedItems(), regionTable);
|
||||||
|
contextChanged();
|
||||||
|
});
|
||||||
|
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
|
||||||
|
regionTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
navigateToSelectedRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
regionTable.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
navigateToSelectedRegion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Adjust default column widths?
|
||||||
|
TableColumnModel columnModel = regionTable.getColumnModel();
|
||||||
|
|
||||||
|
TableColumn startCol = columnModel.getColumn(RegionTableColumns.START.ordinal());
|
||||||
|
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||||
|
|
||||||
|
TableColumn endCol = columnModel.getColumn(RegionTableColumns.END.ordinal());
|
||||||
|
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||||
|
|
||||||
|
TableColumn lenCol = columnModel.getColumn(RegionTableColumns.LENGTH.ordinal());
|
||||||
|
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||||
|
|
||||||
|
final int small = 100;
|
||||||
|
TableColumn rCol = columnModel.getColumn(RegionTableColumns.READ.ordinal());
|
||||||
|
rCol.setPreferredWidth(small);
|
||||||
|
TableColumn wCol = columnModel.getColumn(RegionTableColumns.WRITE.ordinal());
|
||||||
|
wCol.setPreferredWidth(small);
|
||||||
|
TableColumn eCol = columnModel.getColumn(RegionTableColumns.EXECUTE.ordinal());
|
||||||
|
eCol.setPreferredWidth(small);
|
||||||
|
TableColumn vCol = columnModel.getColumn(RegionTableColumns.VOLATILE.ordinal());
|
||||||
|
vCol.setPreferredWidth(small);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRegions() {
|
||||||
|
regionTableModel.clear();
|
||||||
|
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
|
||||||
|
regionTableModel.addAllItems(memoryManager.getAllRegions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerRegionActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isContextNonEmpty(DebuggerRegionActionContext ctx) {
|
||||||
|
return !ctx.getSelectedRegions().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<TraceMemoryRegion> getSelectedRegions(DebuggerRegionActionContext ctx) {
|
||||||
|
if (ctx == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ctx.getSelectedRegions()
|
||||||
|
.stream()
|
||||||
|
.map(r -> r.getRegion())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void navigateToSelectedRegion() {
|
||||||
|
if (listingService != null) {
|
||||||
|
int selectedRow = regionTable.getSelectedRow();
|
||||||
|
int selectedColumn = regionTable.getSelectedColumn();
|
||||||
|
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
|
||||||
|
if (value instanceof Address) {
|
||||||
|
listingService.goTo((Address) value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
|
||||||
|
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable,
|
||||||
|
regionTableModel, regionFilterPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<RegionRow> getSelectedRows() {
|
||||||
|
return regionFilterPanel.getSelectedItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
setTrace(coordinates.getTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrace(Trace trace) {
|
||||||
|
if (currentTrace == trace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeOldListeners();
|
||||||
|
currentTrace = trace;
|
||||||
|
addNewListeners();
|
||||||
|
loadRegions();
|
||||||
|
contextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void contextChanged() {
|
||||||
|
provider.contextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOldListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.removeListener(regionsListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.addListener(regionsListener);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,375 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
|
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.util.HTMLUtilities;
|
||||||
|
|
||||||
|
public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||||
|
implements ListSelectionListener, CellActivationListener {
|
||||||
|
|
||||||
|
private static class RegionKeyColumn extends TraceValueKeyColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Key";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegionPathColumn extends TraceValueKeyColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Path";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
return rowObject.getValue().getCanonicalPath().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegionNameColumn extends TraceValueValColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class ValueAddress extends ValueDerivedProperty<Address> {
|
||||||
|
public ValueAddress(ValueRow row) {
|
||||||
|
super(row, Address.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplay() {
|
||||||
|
Address value = getValue();
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHtmlDisplay() {
|
||||||
|
Address value = getValue();
|
||||||
|
return value == null ? ""
|
||||||
|
: ("<html><body style='font-family:monospaced'>" +
|
||||||
|
HTMLUtilities.escapeHTML(value.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTip() {
|
||||||
|
Address value = getValue();
|
||||||
|
return value == null ? "" : value.toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isModified() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class RegionAddressColumn
|
||||||
|
extends TraceValueObjectPropertyColumn<Address> {
|
||||||
|
public RegionAddressColumn() {
|
||||||
|
super(Address.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Address fromRange(AddressRange range);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||||
|
return new ValueAddress(row) {
|
||||||
|
@Override
|
||||||
|
public Address getValue() {
|
||||||
|
TraceObjectValue entry =
|
||||||
|
row.getAttributeEntry(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||||
|
return entry == null || !(entry.getValue() instanceof AddressRange range)
|
||||||
|
? null
|
||||||
|
: fromRange(range);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegionStartColumn extends RegionAddressColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Start";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Address fromRange(AddressRange range) {
|
||||||
|
return range.getMinAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegionEndColumn extends RegionAddressColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "End";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Address fromRange(AddressRange range) {
|
||||||
|
return range.getMaxAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RegionLengthColumn extends TraceValueObjectPropertyColumn<Long> {
|
||||||
|
public RegionLengthColumn() {
|
||||||
|
super(Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Length";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueProperty<Long> getProperty(ValueRow row) {
|
||||||
|
return new ValueDerivedProperty<>(row, Long.class) {
|
||||||
|
@Override
|
||||||
|
public Long getValue() {
|
||||||
|
TraceObjectValue entry =
|
||||||
|
row.getAttributeEntry(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||||
|
return entry == null || !(entry.getValue() instanceof AddressRange range)
|
||||||
|
? null
|
||||||
|
: range.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplay() {
|
||||||
|
Long value = getValue();
|
||||||
|
return value == null ? "" : ("0x" + Long.toUnsignedString(value, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHtmlDisplay() {
|
||||||
|
Long value = getValue();
|
||||||
|
return value == null ? ""
|
||||||
|
: ("<html><body style='font-family:monospaced'>0x" +
|
||||||
|
Long.toUnsignedString(value, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTip() {
|
||||||
|
return getDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isModified() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class RegionFlagColumn extends TraceValueObjectAttributeColumn<Boolean> {
|
||||||
|
public RegionFlagColumn(String attributeName) {
|
||||||
|
super(attributeName, Boolean.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnPreferredWidth() {
|
||||||
|
return 80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegionReadColumn extends RegionFlagColumn {
|
||||||
|
public RegionReadColumn() {
|
||||||
|
super(TargetMemoryRegion.READABLE_ATTRIBUTE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Read";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegionWriteColumn extends RegionFlagColumn {
|
||||||
|
public RegionWriteColumn() {
|
||||||
|
super(TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Write";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegionExecuteColumn extends RegionFlagColumn {
|
||||||
|
public RegionExecuteColumn() {
|
||||||
|
super(TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Execute";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegionTableModel extends ObjectTableModel {
|
||||||
|
protected RegionTableModel(Plugin plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||||
|
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||||
|
descriptor.addHiddenColumn(new RegionKeyColumn());
|
||||||
|
descriptor.addHiddenColumn(new RegionPathColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionNameColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionStartColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionEndColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionLengthColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionReadColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionWriteColumn());
|
||||||
|
descriptor.addVisibleColumn(new RegionExecuteColumn());
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
|
private DebuggerObjectActionContext myActionContext;
|
||||||
|
|
||||||
|
public DebuggerRegionsPanel(DebuggerRegionsProvider provider) {
|
||||||
|
super(provider.plugin);
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
|
setLimitToSnap(true);
|
||||||
|
setShowHidden(false);
|
||||||
|
|
||||||
|
addSelectionListener(this);
|
||||||
|
addCellActivationListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ObjectTableModel createModel(Plugin plugin) {
|
||||||
|
return new RegionTableModel(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerObjectActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ModelQuery successorRegions(TargetObjectSchema rootSchema, List<String> path) {
|
||||||
|
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
|
||||||
|
return new ModelQuery(schema.searchFor(TargetMemoryRegion.class, path, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ModelQuery computeQuery(TraceObject object) {
|
||||||
|
if (object == null) {
|
||||||
|
return ModelQuery.EMPTY;
|
||||||
|
}
|
||||||
|
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
|
||||||
|
List<String> seedPath = object.getCanonicalPath().getKeyList();
|
||||||
|
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
|
||||||
|
if (processPath != null) {
|
||||||
|
return successorRegions(rootSchema, processPath);
|
||||||
|
}
|
||||||
|
List<String> memoryPath = rootSchema.searchForSuitable(TargetMemory.class, seedPath);
|
||||||
|
if (memoryPath != null) {
|
||||||
|
return successorRegions(rootSchema, memoryPath);
|
||||||
|
}
|
||||||
|
return successorRegions(rootSchema, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
TraceObject object = coordinates.getObject();
|
||||||
|
setQuery(computeQuery(object));
|
||||||
|
goToCoordinates(coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isContextNonEmpty(DebuggerObjectActionContext ctx) {
|
||||||
|
return ctx != null && !ctx.getObjectValues().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Set<TraceMemoryRegion> getSelectedRegions(DebuggerObjectActionContext ctx) {
|
||||||
|
return ctx == null ? null
|
||||||
|
: ctx.getObjectValues()
|
||||||
|
.stream()
|
||||||
|
.filter(v -> v.isObject())
|
||||||
|
.map(v -> v.getChild().queryInterface(TraceObjectMemoryRegion.class))
|
||||||
|
.filter(r -> r != null)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
|
||||||
|
trySelect(sel.stream()
|
||||||
|
.filter(r -> r instanceof TraceObjectMemoryRegion)
|
||||||
|
.map(r -> ((TraceObjectMemoryRegion) r).getObject())
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void valueChanged(ListSelectionEvent e) {
|
||||||
|
if (e.getValueIsAdjusting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ValueRow> sel = getSelectedItems();
|
||||||
|
if (!sel.isEmpty()) {
|
||||||
|
myActionContext = new DebuggerObjectActionContext(
|
||||||
|
sel.stream().map(r -> r.getValue()).collect(Collectors.toList()), provider, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cellActivated(JTable table) {
|
||||||
|
if (provider.listingService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int row = table.getSelectedRow();
|
||||||
|
int col = table.getSelectedColumn();
|
||||||
|
Object value = table.getValueAt(row, col);
|
||||||
|
if (!(value instanceof ValueProperty<?> property)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object propVal = property.getValue();
|
||||||
|
if (propVal instanceof Address address) {
|
||||||
|
provider.listingService.goTo(address, true);
|
||||||
|
}
|
||||||
|
else if (propVal instanceof AddressRange range) {
|
||||||
|
provider.listingService.setCurrentSelection(
|
||||||
|
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
|
||||||
|
provider.listingService.goTo(range.getMinAddress(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
|
||||||
}
|
}
|
||||||
else if (event instanceof TraceActivatedPluginEvent) {
|
else if (event instanceof TraceActivatedPluginEvent) {
|
||||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||||
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,52 +16,58 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.memory;
|
package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.event.*;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.table.TableColumn;
|
|
||||||
import javax.swing.table.TableColumnModel;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.WindowPosition;
|
import docking.WindowPosition;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
import docking.widgets.table.CustomToStringCellRenderer;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand;
|
import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand;
|
||||||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||||
import ghidra.framework.model.DomainObject;
|
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.ObjectKey;
|
|
||||||
import ghidra.util.table.GhidraTable;
|
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
|
||||||
|
|
||||||
public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
|
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||||
|
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.getSnap() != b.getSnap()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(a.getObject(), b.getObject())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
interface MapRegionsAction {
|
interface MapRegionsAction {
|
||||||
String NAME = DebuggerResources.NAME_MAP_REGIONS;
|
String NAME = DebuggerResources.NAME_MAP_REGIONS;
|
||||||
String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_REGIONS;
|
String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_REGIONS;
|
||||||
|
@ -108,122 +114,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected enum RegionTableColumns
|
|
||||||
implements EnumeratedTableColumn<RegionTableColumns, RegionRow> {
|
|
||||||
NAME("Name", String.class, RegionRow::getName, RegionRow::setName),
|
|
||||||
LIFESPAN("Lifespan", Lifespan.class, RegionRow::getLifespan),
|
|
||||||
START("Start", Address.class, RegionRow::getMinAddress),
|
|
||||||
END("End", Address.class, RegionRow::getMaxAddress),
|
|
||||||
LENGTH("Length", Long.class, RegionRow::getLength),
|
|
||||||
READ("Read", Boolean.class, RegionRow::isRead, RegionRow::setRead),
|
|
||||||
WRITE("Write", Boolean.class, RegionRow::isWrite, RegionRow::setWrite),
|
|
||||||
EXECUTE("Execute", Boolean.class, RegionRow::isExecute, RegionRow::setExecute),
|
|
||||||
VOLATILE("Volatile", Boolean.class, RegionRow::isVolatile, RegionRow::setVolatile);
|
|
||||||
|
|
||||||
private final String header;
|
|
||||||
private final Function<RegionRow, ?> getter;
|
|
||||||
private final BiConsumer<RegionRow, Object> setter;
|
|
||||||
private final Class<?> cls;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter,
|
|
||||||
BiConsumer<RegionRow, T> setter) {
|
|
||||||
this.header = header;
|
|
||||||
this.cls = cls;
|
|
||||||
this.getter = getter;
|
|
||||||
this.setter = (BiConsumer<RegionRow, Object>) setter;
|
|
||||||
}
|
|
||||||
|
|
||||||
<T> RegionTableColumns(String header, Class<T> cls, Function<RegionRow, T> getter) {
|
|
||||||
this(header, cls, getter, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getValueClass() {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEditable(RegionRow row) {
|
|
||||||
return setter != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValueOf(RegionRow row, Object value) {
|
|
||||||
setter.accept(row, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueOf(RegionRow row) {
|
|
||||||
return getter.apply(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class RegionTableModel
|
|
||||||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
|
||||||
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
|
|
||||||
|
|
||||||
public RegionTableModel(PluginTool tool) {
|
|
||||||
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
|
|
||||||
RegionRow::new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static RegionRow getSelectedRegionRow(ActionContext context) {
|
|
||||||
if (!(context instanceof DebuggerRegionActionContext)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
|
|
||||||
Set<RegionRow> regions = ctx.getSelectedRegions();
|
|
||||||
if (regions.size() != 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return regions.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
|
|
||||||
if (!(context instanceof DebuggerRegionActionContext)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
DebuggerRegionActionContext ctx = (DebuggerRegionActionContext) context;
|
|
||||||
return ctx.getSelectedRegions()
|
|
||||||
.stream()
|
|
||||||
.map(r -> r.getRegion())
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RegionsListener extends TraceDomainObjectListener {
|
|
||||||
public RegionsListener() {
|
|
||||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
|
||||||
|
|
||||||
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionAdded);
|
|
||||||
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
|
|
||||||
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
|
|
||||||
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionDeleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void objectRestored() {
|
|
||||||
loadRegions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void regionAdded(TraceMemoryRegion region) {
|
|
||||||
regionTableModel.addItem(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void regionChanged(TraceMemoryRegion region) {
|
|
||||||
regionTableModel.updateItem(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void regionDeleted(TraceMemoryRegion region) {
|
|
||||||
regionTableModel.deleteItem(region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class SelectAddressesAction extends AbstractSelectAddressesAction {
|
protected class SelectAddressesAction extends AbstractSelectAddressesAction {
|
||||||
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
|
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||||
|
|
||||||
|
@ -241,7 +131,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
if (listingService == null) {
|
if (listingService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Set<TraceMemoryRegion> regions = getSelectedRegions(myActionContext);
|
Set<TraceMemoryRegion> regions = getSelectedRegions(context);
|
||||||
if (regions == null) {
|
if (regions == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -255,42 +145,36 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
public boolean isEnabledForContext(ActionContext context) {
|
||||||
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
|
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
|
||||||
return sel != null && !sel.isEmpty();
|
return sel != null && !sel.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DebuggerRegionsPlugin plugin;
|
final DebuggerRegionsPlugin plugin;
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
ProgramManager programManager;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
DebuggerListingService listingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerStaticMappingService staticMappingService;
|
private DebuggerStaticMappingService staticMappingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
@AutoServiceConsumed
|
|
||||||
private DebuggerListingService listingService;
|
|
||||||
@AutoServiceConsumed
|
|
||||||
ProgramManager programManager;
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
private Trace currentTrace;
|
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
private Program currentProgram;
|
||||||
private final RegionsListener regionsListener = new RegionsListener();
|
private ProgramLocation currentLocation;
|
||||||
|
|
||||||
protected final RegionTableModel regionTableModel;
|
|
||||||
protected GhidraTable regionTable;
|
|
||||||
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
|
|
||||||
|
|
||||||
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
DebuggerRegionsPanel panel;
|
||||||
|
DebuggerLegacyRegionsPanel legacyPanel;
|
||||||
|
|
||||||
// TODO: Lazy construction of these dialogs?
|
// TODO: Lazy construction of these dialogs?
|
||||||
private final DebuggerBlockChooserDialog blockChooserDialog;
|
private final DebuggerBlockChooserDialog blockChooserDialog;
|
||||||
private final DebuggerRegionMapProposalDialog regionProposalDialog;
|
private final DebuggerRegionMapProposalDialog regionProposalDialog;
|
||||||
|
|
||||||
private DebuggerRegionActionContext myActionContext;
|
|
||||||
private Program currentProgram;
|
|
||||||
private ProgramLocation currentLocation;
|
|
||||||
|
|
||||||
DockingAction actionMapRegions;
|
DockingAction actionMapRegions;
|
||||||
DockingAction actionMapRegionTo;
|
DockingAction actionMapRegionTo;
|
||||||
DockingAction actionMapRegionsTo;
|
DockingAction actionMapRegionsTo;
|
||||||
|
@ -304,8 +188,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
DebuggerRegionActionContext.class);
|
DebuggerRegionActionContext.class);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
regionTableModel = new RegionTableModel(tool);
|
|
||||||
|
|
||||||
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
|
setIcon(DebuggerResources.ICON_PROVIDER_REGIONS);
|
||||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
|
setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS);
|
||||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||||
|
@ -322,103 +204,28 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected static boolean isLegacy(Trace trace) {
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
return trace != null && trace.getObjectManager().getRootSchema() == null;
|
||||||
if (myActionContext == null) {
|
|
||||||
return super.getActionContext(event);
|
|
||||||
}
|
|
||||||
return myActionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadRegions() {
|
|
||||||
regionTableModel.clear();
|
|
||||||
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
|
|
||||||
regionTableModel.addAllItems(memoryManager.getAllRegions());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildMainPanel() {
|
protected void buildMainPanel() {
|
||||||
regionTable = new GhidraTable(regionTableModel);
|
panel = new DebuggerRegionsPanel(this);
|
||||||
regionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
mainPanel.add(panel);
|
||||||
mainPanel.add(new JScrollPane(regionTable));
|
legacyPanel = new DebuggerLegacyRegionsPanel(this);
|
||||||
regionFilterPanel = new GhidraTableFilterPanel<>(regionTable, regionTableModel);
|
|
||||||
mainPanel.add(regionFilterPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
regionTable.getSelectionModel().addListSelectionListener(evt -> {
|
|
||||||
myActionContext = new DebuggerRegionActionContext(this,
|
|
||||||
regionFilterPanel.getSelectedItems(), regionTable);
|
|
||||||
contextChanged();
|
|
||||||
});
|
|
||||||
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
|
|
||||||
regionTable.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
if (e.getClickCount() == 2) {
|
|
||||||
navigateToSelectedRegion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
regionTable.addKeyListener(new KeyAdapter() {
|
|
||||||
@Override
|
|
||||||
public void keyPressed(KeyEvent e) {
|
|
||||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
|
||||||
navigateToSelectedRegion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Adjust default column widths?
|
|
||||||
TableColumnModel columnModel = regionTable.getColumnModel();
|
|
||||||
|
|
||||||
TableColumn startCol = columnModel.getColumn(RegionTableColumns.START.ordinal());
|
|
||||||
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
|
||||||
|
|
||||||
TableColumn endCol = columnModel.getColumn(RegionTableColumns.END.ordinal());
|
|
||||||
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
|
||||||
|
|
||||||
TableColumn lenCol = columnModel.getColumn(RegionTableColumns.LENGTH.ordinal());
|
|
||||||
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
|
||||||
|
|
||||||
final int small = 100;
|
|
||||||
TableColumn rCol = columnModel.getColumn(RegionTableColumns.READ.ordinal());
|
|
||||||
rCol.setPreferredWidth(small);
|
|
||||||
TableColumn wCol = columnModel.getColumn(RegionTableColumns.WRITE.ordinal());
|
|
||||||
wCol.setPreferredWidth(small);
|
|
||||||
TableColumn eCol = columnModel.getColumn(RegionTableColumns.EXECUTE.ordinal());
|
|
||||||
eCol.setPreferredWidth(small);
|
|
||||||
TableColumn vCol = columnModel.getColumn(RegionTableColumns.VOLATILE.ordinal());
|
|
||||||
vCol.setPreferredWidth(small);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void navigateToSelectedRegion() {
|
|
||||||
if (listingService != null) {
|
|
||||||
int selectedRow = regionTable.getSelectedRow();
|
|
||||||
int selectedColumn = regionTable.getSelectedColumn();
|
|
||||||
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
|
|
||||||
if (value instanceof Address) {
|
|
||||||
listingService.goTo((Address) value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
actionMapRegions = MapRegionsAction.builder(plugin)
|
actionMapRegions = MapRegionsAction.builder(plugin)
|
||||||
.withContext(DebuggerRegionActionContext.class)
|
|
||||||
.enabledWhen(this::isContextNonEmpty)
|
.enabledWhen(this::isContextNonEmpty)
|
||||||
.popupWhen(this::isContextNonEmpty)
|
.popupWhen(this::isContextNonEmpty)
|
||||||
.onAction(this::activatedMapRegions)
|
.onAction(this::activatedMapRegions)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionMapRegionTo = MapRegionToAction.builder(plugin)
|
actionMapRegionTo = MapRegionToAction.builder(plugin)
|
||||||
.withContext(DebuggerRegionActionContext.class)
|
.enabledWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx))
|
||||||
.enabledWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1)
|
.popupWhen(ctx -> currentProgram != null && isContextSingleSelection(ctx))
|
||||||
.popupWhen(ctx -> currentProgram != null && ctx.getSelectedRegions().size() == 1)
|
|
||||||
.onAction(this::activatedMapRegionTo)
|
.onAction(this::activatedMapRegionTo)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionMapRegionsTo = MapRegionsToAction.builder(plugin)
|
actionMapRegionsTo = MapRegionsToAction.builder(plugin)
|
||||||
.withContext(DebuggerRegionActionContext.class)
|
|
||||||
.enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
|
.enabledWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
|
||||||
.popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
|
.popupWhen(ctx -> currentProgram != null && isContextNonEmpty(ctx))
|
||||||
.onAction(this::activatedMapRegionsTo)
|
.onAction(this::activatedMapRegionsTo)
|
||||||
|
@ -426,50 +233,76 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
actionSelectAddresses = new SelectAddressesAction();
|
actionSelectAddresses = new SelectAddressesAction();
|
||||||
actionSelectRows = SelectRowsAction.builder(plugin)
|
actionSelectRows = SelectRowsAction.builder(plugin)
|
||||||
.description("Select regions by trace selection")
|
.description("Select regions by trace selection")
|
||||||
.enabledWhen(ctx -> currentTrace != null)
|
.enabledWhen(ctx -> current.getTrace() != null)
|
||||||
.onAction(this::activatedSelectCurrent)
|
.onAction(this::activatedSelectCurrent)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
actionForceFullView = ForceFullViewAction.builder(plugin)
|
actionForceFullView = ForceFullViewAction.builder(plugin)
|
||||||
.enabledWhen(ctx -> currentTrace != null)
|
.enabledWhen(ctx -> current.getTrace() != null)
|
||||||
.onAction(this::activatedForceFullView)
|
.onAction(this::activatedForceFullView)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isContextNonEmpty(DebuggerRegionActionContext ctx) {
|
@Override
|
||||||
return !ctx.getSelectedRegions().isEmpty();
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
}
|
final ActionContext context;
|
||||||
|
if (isLegacy(current.getTrace())) {
|
||||||
private static Set<TraceMemoryRegion> getSelectedRegions(DebuggerRegionActionContext ctx) {
|
context = legacyPanel.getActionContext();
|
||||||
if (ctx == null) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return ctx.getSelectedRegions()
|
else {
|
||||||
.stream()
|
context = panel.getActionContext();
|
||||||
.map(r -> r.getRegion())
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedMapRegions(DebuggerRegionActionContext ignored) {
|
|
||||||
mapRegions(getSelectedRegions(myActionContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedMapRegionsTo(DebuggerRegionActionContext ignored) {
|
|
||||||
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
|
|
||||||
if (sel == null || sel.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
mapRegionsTo(sel);
|
if (context != null) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
return super.getActionContext(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedMapRegionTo(DebuggerRegionActionContext ignored) {
|
private boolean isContextNonEmpty(ActionContext context) {
|
||||||
Set<TraceMemoryRegion> sel = getSelectedRegions(myActionContext);
|
if (context instanceof DebuggerRegionActionContext legacyCtx) {
|
||||||
|
return legacyPanel.isContextNonEmpty(legacyCtx);
|
||||||
|
}
|
||||||
|
else if (context instanceof DebuggerObjectActionContext ctx) {
|
||||||
|
return panel.isContextNonEmpty(ctx);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isContextSingleSelection(ActionContext context) {
|
||||||
|
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
|
||||||
|
return sel != null && sel.size() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<TraceMemoryRegion> getSelectedRegions(ActionContext context) {
|
||||||
|
if (context instanceof DebuggerRegionActionContext legacyCtx) {
|
||||||
|
return DebuggerLegacyRegionsPanel.getSelectedRegions(legacyCtx);
|
||||||
|
}
|
||||||
|
else if (context instanceof DebuggerObjectActionContext ctx) {
|
||||||
|
return DebuggerRegionsPanel.getSelectedRegions(ctx);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activatedMapRegions(ActionContext context) {
|
||||||
|
mapRegions(getSelectedRegions(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activatedMapRegionTo(ActionContext context) {
|
||||||
|
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
|
||||||
if (sel == null || sel.size() != 1) {
|
if (sel == null || sel.size() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mapRegionTo(sel.iterator().next());
|
mapRegionTo(sel.iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activatedMapRegionsTo(ActionContext context) {
|
||||||
|
Set<TraceMemoryRegion> sel = getSelectedRegions(context);
|
||||||
|
if (sel == null || sel.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mapRegionsTo(sel);
|
||||||
|
}
|
||||||
|
|
||||||
protected void promptRegionProposal(Collection<RegionMapEntry> proposal) {
|
protected void promptRegionProposal(Collection<RegionMapEntry> proposal) {
|
||||||
if (proposal.isEmpty()) {
|
if (proposal.isEmpty()) {
|
||||||
Msg.showInfo(this, getComponent(), "Map Regions",
|
Msg.showInfo(this, getComponent(), "Map Regions",
|
||||||
|
@ -483,7 +316,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tool.executeBackgroundCommand(
|
tool.executeBackgroundCommand(
|
||||||
new MapRegionsBackgroundCommand(staticMappingService, adjusted), currentTrace);
|
new MapRegionsBackgroundCommand(staticMappingService, adjusted), current.getTrace());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void mapRegions(Set<TraceMemoryRegion> regions) {
|
protected void mapRegions(Set<TraceMemoryRegion> regions) {
|
||||||
|
@ -524,13 +357,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedSelectCurrent(ActionContext ignored) {
|
private void activatedSelectCurrent(ActionContext ignored) {
|
||||||
if (listingService == null || traceManager == null || currentTrace == null) {
|
if (listingService == null || traceManager == null || current.getTrace() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Select from other listings?
|
// TODO: Select from other listings?
|
||||||
ProgramSelection progSel = listingService.getCurrentSelection();
|
ProgramSelection progSel = listingService.getCurrentSelection();
|
||||||
|
|
||||||
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
|
TraceMemoryManager memoryManager = current.getTrace().getMemoryManager();
|
||||||
if (progSel != null && !progSel.isEmpty()) {
|
if (progSel != null && !progSel.isEmpty()) {
|
||||||
Set<TraceMemoryRegion> regSel = new HashSet<>();
|
Set<TraceMemoryRegion> regSel = new HashSet<>();
|
||||||
for (AddressRange range : progSel) {
|
for (AddressRange range : progSel) {
|
||||||
|
@ -552,21 +385,22 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedForceFullView(ActionContext ignored) {
|
private void activatedForceFullView(ActionContext ignored) {
|
||||||
if (currentTrace == null) {
|
if (current.getTrace() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentTrace.getProgramView()
|
current.getTrace()
|
||||||
|
.getProgramView()
|
||||||
.getMemory()
|
.getMemory()
|
||||||
.setForceFullView(actionForceFullView.isSelected());
|
.setForceFullView(actionForceFullView.isSelected());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
|
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
|
||||||
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable,
|
if (isLegacy(current.getTrace())) {
|
||||||
regionTableModel, regionFilterPanel);
|
legacyPanel.setSelectedRegions(sel);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
public Collection<RegionRow> getSelectedRows() {
|
panel.setSelectedRegions(sel);
|
||||||
return regionFilterPanel.getSelectedItems();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -602,41 +436,17 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrace(Trace trace) {
|
|
||||||
if (currentTrace == trace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeOldListeners();
|
|
||||||
currentTrace = trace;
|
|
||||||
addNewListeners();
|
|
||||||
loadRegions();
|
|
||||||
contextChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void contextChanged() {
|
public void contextChanged() {
|
||||||
super.contextChanged();
|
super.contextChanged();
|
||||||
if (currentTrace != null) {
|
if (current.getTrace() != null) {
|
||||||
actionForceFullView.setSelected(currentTrace.getProgramView()
|
actionForceFullView.setSelected(current.getTrace()
|
||||||
|
.getProgramView()
|
||||||
.getMemory()
|
.getMemory()
|
||||||
.isForceFullView());
|
.isForceFullView());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeOldListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.removeListener(regionsListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.addListener(regionsListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry<Program, MemoryBlock> askBlock(TraceMemoryRegion region, Program program,
|
public Entry<Program, MemoryBlock> askBlock(TraceMemoryRegion region, Program program,
|
||||||
MemoryBlock block) {
|
MemoryBlock block) {
|
||||||
if (programManager == null) {
|
if (programManager == null) {
|
||||||
|
@ -646,4 +456,32 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||||
return blockChooserDialog.chooseBlock(getTool(), region,
|
return blockChooserDialog.chooseBlock(getTool(), region,
|
||||||
List.of(programManager.getAllOpenPrograms()));
|
List.of(programManager.getAllOpenPrograms()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
if (sameCoordinates(current, coordinates)) {
|
||||||
|
current = coordinates;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = coordinates;
|
||||||
|
|
||||||
|
if (isLegacy(coordinates.getTrace())) {
|
||||||
|
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||||
|
legacyPanel.coordinatesActivated(coordinates);
|
||||||
|
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {
|
||||||
|
mainPanel.remove(panel);
|
||||||
|
mainPanel.add(legacyPanel);
|
||||||
|
mainPanel.validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
legacyPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||||
|
panel.coordinatesActivated(coordinates);
|
||||||
|
if (ArrayUtils.indexOf(mainPanel.getComponents(), panel) == -1) {
|
||||||
|
mainPanel.remove(legacyPanel);
|
||||||
|
mainPanel.add(panel);
|
||||||
|
mainPanel.validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.stream.Stream;
|
||||||
|
|
||||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||||
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
|
@ -36,6 +37,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
|
|
||||||
protected class ListenerForChanges extends TraceDomainObjectListener {
|
protected class ListenerForChanges extends TraceDomainObjectListener {
|
||||||
public ListenerForChanges() {
|
public ListenerForChanges() {
|
||||||
|
listenForUntyped(Trace.DO_OBJECT_RESTORED, this::objectRestored);
|
||||||
listenFor(TraceObjectChangeType.VALUE_CREATED, this::valueCreated);
|
listenFor(TraceObjectChangeType.VALUE_CREATED, this::valueCreated);
|
||||||
listenFor(TraceObjectChangeType.VALUE_DELETED, this::valueDeleted);
|
listenFor(TraceObjectChangeType.VALUE_DELETED, this::valueDeleted);
|
||||||
listenFor(TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, this::valueLifespanChanged);
|
listenFor(TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, this::valueLifespanChanged);
|
||||||
|
@ -44,6 +46,10 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
listenFor(TraceSnapshotChangeType.DELETED, this::maxSnapChanged);
|
listenFor(TraceSnapshotChangeType.DELETED, this::maxSnapChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void objectRestored(DomainObjectChangeRecord record) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
protected void valueCreated(TraceObjectValue value) {
|
protected void valueCreated(TraceObjectValue value) {
|
||||||
if (query != null && query.involves(span, value)) {
|
if (query != null && query.involves(span, value)) {
|
||||||
reload(); // Can I be more surgical?
|
reload(); // Can I be more surgical?
|
||||||
|
|
|
@ -17,12 +17,11 @@ package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.*;
|
||||||
import java.awt.event.MouseListener;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.*;
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import docking.widgets.table.DynamicTableColumn;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
|
@ -32,12 +31,17 @@ import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableModel<T>>
|
public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableModel<T>>
|
||||||
extends JPanel {
|
extends JPanel {
|
||||||
|
|
||||||
|
public interface CellActivationListener {
|
||||||
|
void cellActivated(JTable table);
|
||||||
|
}
|
||||||
|
|
||||||
protected final M tableModel;
|
protected final M tableModel;
|
||||||
protected final GhidraTable table;
|
protected final GhidraTable table;
|
||||||
protected final GhidraTableFilterPanel<T> filterPanel;
|
protected final GhidraTableFilterPanel<T> filterPanel;
|
||||||
|
@ -46,6 +50,9 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
protected boolean limitToSnap = false;
|
protected boolean limitToSnap = false;
|
||||||
protected boolean showHidden = false;
|
protected boolean showHidden = false;
|
||||||
|
|
||||||
|
private final ListenerSet<CellActivationListener> cellActivationListeners =
|
||||||
|
new ListenerSet<>(CellActivationListener.class);
|
||||||
|
|
||||||
public AbstractQueryTablePanel(Plugin plugin) {
|
public AbstractQueryTablePanel(Plugin plugin) {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
tableModel = createModel(plugin);
|
tableModel = createModel(plugin);
|
||||||
|
@ -54,6 +61,24 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
|
|
||||||
add(new JScrollPane(table), BorderLayout.CENTER);
|
add(new JScrollPane(table), BorderLayout.CENTER);
|
||||||
add(filterPanel, BorderLayout.SOUTH);
|
add(filterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
table.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
fireCellActivated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
table.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
fireCellActivated();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract M createModel(Plugin plugin);
|
protected abstract M createModel(Plugin plugin);
|
||||||
|
@ -121,32 +146,12 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
table.getSelectionModel().removeListSelectionListener(listener);
|
table.getSelectionModel().removeListSelectionListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void addCellActivationListener(CellActivationListener listener) {
|
||||||
public synchronized void addMouseListener(MouseListener l) {
|
cellActivationListeners.add(listener);
|
||||||
super.addMouseListener(l);
|
|
||||||
// HACK?
|
|
||||||
table.addMouseListener(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void removeCellActivationListener(CellActivationListener listener) {
|
||||||
public synchronized void removeMouseListener(MouseListener l) {
|
cellActivationListeners.remove(listener);
|
||||||
super.removeMouseListener(l);
|
|
||||||
// HACK?
|
|
||||||
table.removeMouseListener(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void addKeyListener(KeyListener l) {
|
|
||||||
super.addKeyListener(l);
|
|
||||||
// HACK?
|
|
||||||
table.addKeyListener(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void removeKeyListener(KeyListener l) {
|
|
||||||
super.removeKeyListener(l);
|
|
||||||
// HACK?
|
|
||||||
table.removeKeyListener(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSeekListener(SeekListener listener) {
|
public void addSeekListener(SeekListener listener) {
|
||||||
|
@ -161,15 +166,20 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
return table.getSelectionModel().getSelectionMode();
|
return table.getSelectionModel().getSelectionMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: setSelectedItems? Is a bit more work than expected:
|
|
||||||
// see filterPanel.getTableFilterModel();
|
|
||||||
// see table.getSelectionMode().addSelectionInterval()
|
|
||||||
// seems like setSelectedItems should be in filterPanel?
|
|
||||||
|
|
||||||
public void setSelectedItem(T item) {
|
public void setSelectedItem(T item) {
|
||||||
filterPanel.setSelectedItem(item);
|
filterPanel.setSelectedItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelectedItems(Collection<T> items) {
|
||||||
|
table.clearSelection();
|
||||||
|
for (T t : items) {
|
||||||
|
int modelRow = tableModel.getRowIndex(t);
|
||||||
|
int viewRow = filterPanel.getViewRow(modelRow);
|
||||||
|
table.getSelectionModel().addSelectionInterval(viewRow, viewRow);
|
||||||
|
}
|
||||||
|
table.scrollToSelectedRow();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean trySelect(TraceObject object) {
|
public boolean trySelect(TraceObject object) {
|
||||||
T t = tableModel.findTraceObject(object);
|
T t = tableModel.findTraceObject(object);
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
|
@ -179,6 +189,15 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean trySelect(Collection<TraceObject> objects) {
|
||||||
|
List<T> ts = objects.stream().map(tableModel::findTraceObject).collect(Collectors.toList());
|
||||||
|
if (ts.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setSelectedItems(ts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public List<T> getSelectedItems() {
|
public List<T> getSelectedItems() {
|
||||||
return filterPanel.getSelectedItems();
|
return filterPanel.getSelectedItems();
|
||||||
}
|
}
|
||||||
|
@ -192,7 +211,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <V> DynamicTableColumn<T, V, Trace> getColumnByNameAndType(String name, Class<V> type) {
|
public <V> Map.Entry<Integer, DynamicTableColumn<T, V, Trace>> getColumnByNameAndType(
|
||||||
|
String name, Class<V> type) {
|
||||||
int count = tableModel.getColumnCount();
|
int count = tableModel.getColumnCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
DynamicTableColumn<T, ?, ?> column = tableModel.getColumn(i);
|
DynamicTableColumn<T, ?, ?> column = tableModel.getColumn(i);
|
||||||
|
@ -202,7 +222,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
if (column.getColumnClass() != type) {
|
if (column.getColumnClass() != type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return (DynamicTableColumn<T, V, Trace>) column;
|
return Map.entry(table.convertColumnIndexToView(i),
|
||||||
|
(DynamicTableColumn<T, V, Trace>) column);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -214,4 +235,8 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||||
public void setDiffColorSel(Color diffColorSel) {
|
public void setDiffColorSel(Color diffColorSel) {
|
||||||
tableModel.setDiffColorSel(diffColorSel);
|
tableModel.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void fireCellActivated() {
|
||||||
|
cellActivationListeners.fire.cellActivated(table);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
|
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
||||||
|
@ -106,13 +107,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
ToggleDockingAction actionShowPrimitivesInTree;
|
ToggleDockingAction actionShowPrimitivesInTree;
|
||||||
ToggleDockingAction actionShowMethodsInTree;
|
ToggleDockingAction actionShowMethodsInTree;
|
||||||
DockingAction actionFollowLink;
|
DockingAction actionFollowLink;
|
||||||
// TODO: Remove stopgap
|
|
||||||
DockingAction actionStepBackward;
|
|
||||||
DockingAction actionStepForward;
|
|
||||||
|
|
||||||
DebuggerObjectActionContext myActionContext;
|
DebuggerObjectActionContext myActionContext;
|
||||||
|
|
||||||
private SeekListener seekListener = pos -> {
|
private final CellActivationListener elementActivationListener =
|
||||||
|
table -> activatedElementsTable();
|
||||||
|
private final CellActivationListener attributeActivationListener =
|
||||||
|
table -> activatedAttributesTable();
|
||||||
|
|
||||||
|
private final SeekListener seekListener = pos -> {
|
||||||
long snap = Math.round(pos);
|
long snap = Math.round(pos);
|
||||||
if (current.getTrace() == null || snap < 0) {
|
if (current.getTrace() == null || snap < 0) {
|
||||||
snap = 0;
|
snap = 0;
|
||||||
|
@ -320,44 +323,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
elementsTablePanel.addMouseListener(new MouseAdapter() {
|
elementsTablePanel.addCellActivationListener(elementActivationListener);
|
||||||
@Override
|
attributesTablePanel.addCellActivationListener(attributeActivationListener);
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
activatedElementsTable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
elementsTablePanel.addKeyListener(new KeyAdapter() {
|
|
||||||
@Override
|
|
||||||
public void keyPressed(KeyEvent e) {
|
|
||||||
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
activatedElementsTable();
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
attributesTablePanel.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
activatedAttributesTable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
attributesTablePanel.addKeyListener(new KeyAdapter() {
|
|
||||||
@Override
|
|
||||||
public void keyPressed(KeyEvent e) {
|
|
||||||
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
activatedAttributesTable();
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
elementsTablePanel.addSeekListener(seekListener);
|
elementsTablePanel.addSeekListener(seekListener);
|
||||||
attributesTablePanel.addSeekListener(seekListener);
|
attributesTablePanel.addSeekListener(seekListener);
|
||||||
|
@ -393,16 +360,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
.enabledWhen(this::hasSingleLink)
|
.enabledWhen(this::hasSingleLink)
|
||||||
.onAction(this::activatedFollowLink)
|
.onAction(this::activatedFollowLink)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
|
|
||||||
// TODO: These are a stopgap until the plot column header provides nav
|
|
||||||
actionStepBackward = StepSnapBackwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isStepBackwardEnabled)
|
|
||||||
.onAction(this::activatedStepBackward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionStepForward = StepSnapForwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isStepForwardEnabled)
|
|
||||||
.onAction(this::activatedStepForward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedElementsTable() {
|
private void activatedElementsTable() {
|
||||||
|
@ -480,44 +437,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
|
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStepBackwardEnabled(ActionContext ignored) {
|
|
||||||
if (current.getTrace() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!current.getTime().isSnapOnly()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (current.getSnap() <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedStepBackward(ActionContext ignored) {
|
|
||||||
if (current.getTime().isSnapOnly()) {
|
|
||||||
traceManager.activateSnap(current.getSnap() - 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
traceManager.activateSnap(current.getSnap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStepForwardEnabled(ActionContext ignored) {
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
|
|
||||||
if (maxSnap == null || current.getSnap() >= maxSnap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedStepForward(ActionContext ignored) {
|
|
||||||
traceManager.activateSnap(current.getSnap() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JComponent getComponent() {
|
public JComponent getComponent() {
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
|
@ -533,7 +452,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TraceObject parent = trace.getObjectManager().getObjectByCanonicalPath(parentPath);
|
TraceObject parent = trace.getObjectManager().getObjectByCanonicalPath(parentPath);
|
||||||
// TODO: Require parent to be a canonical container?
|
// Should we require parent to be a canonical container?
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,86 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ValueProperty<T> {
|
||||||
|
public Class<T> getType();
|
||||||
|
|
||||||
|
public ValueRow getRow();
|
||||||
|
|
||||||
|
public T getValue();
|
||||||
|
|
||||||
|
public String getDisplay();
|
||||||
|
|
||||||
|
public String getHtmlDisplay();
|
||||||
|
|
||||||
|
public String getToolTip();
|
||||||
|
|
||||||
|
public boolean isModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class ValueDerivedProperty<T> implements ValueProperty<T> {
|
||||||
|
protected final ValueRow row;
|
||||||
|
protected final Class<T> type;
|
||||||
|
|
||||||
|
public ValueDerivedProperty(ValueRow row, Class<T> type) {
|
||||||
|
this.row = row;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRow getRow() {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ValueAttribute<T> (ValueRow row, String name, Class<T> type)
|
||||||
|
implements ValueProperty<T> {
|
||||||
|
public TraceObjectValue getEntry() {
|
||||||
|
return row.getAttributeEntry(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRow getRow() {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getValue() {
|
||||||
|
TraceObjectValue entry = row.getAttributeEntry(name);
|
||||||
|
return entry == null || !type.isInstance(entry.getValue()) ? null
|
||||||
|
: type.cast(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplay() {
|
||||||
|
return row.getAttributeDisplay(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHtmlDisplay() {
|
||||||
|
return row.getAttributeHtmlDisplay(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToolTip() {
|
||||||
|
return row.getAttributeToolTip(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isModified() {
|
||||||
|
return row.isAttributeModified(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface ValueRow {
|
public interface ValueRow {
|
||||||
String getKey();
|
String getKey();
|
||||||
|
|
||||||
|
@ -79,7 +159,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
*/
|
*/
|
||||||
boolean isModified();
|
boolean isModified();
|
||||||
|
|
||||||
TraceObjectValue getAttribute(String attributeName);
|
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
|
||||||
|
return new ValueAttribute<>(this, attributeName, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceObjectValue getAttributeEntry(String attributeName);
|
||||||
|
|
||||||
String getAttributeDisplay(String attributeName);
|
String getAttributeDisplay(String attributeName);
|
||||||
|
|
||||||
|
@ -143,7 +227,7 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectValue getAttribute(String attributeName) {
|
public TraceObjectValue getAttributeEntry(String attributeName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,28 +280,28 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceObjectValue getAttribute(String attributeName) {
|
public TraceObjectValue getAttributeEntry(String attributeName) {
|
||||||
return object.getAttribute(getSnap(), attributeName);
|
return object.getAttribute(getSnap(), attributeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeDisplay(String attributeName) {
|
public String getAttributeDisplay(String attributeName) {
|
||||||
return display.getEdgeDisplay(getAttribute(attributeName));
|
return display.getEdgeDisplay(getAttributeEntry(attributeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeHtmlDisplay(String attributeName) {
|
public String getAttributeHtmlDisplay(String attributeName) {
|
||||||
return display.getEdgeHtmlDisplay(getAttribute(attributeName));
|
return display.getEdgeHtmlDisplay(getAttributeEntry(attributeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeToolTip(String attributeName) {
|
public String getAttributeToolTip(String attributeName) {
|
||||||
return display.getEdgeToolTip(getAttribute(attributeName));
|
return display.getEdgeToolTip(getAttributeEntry(attributeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAttributeModified(String attributeName) {
|
public boolean isAttributeModified(String attributeName) {
|
||||||
return isValueModified(getAttribute(attributeName));
|
return isValueModified(getAttributeEntry(attributeName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,21 +353,21 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class AutoAttributeColumn extends TraceValueObjectAttributeColumn {
|
static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {
|
||||||
public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx,
|
public static TraceValueObjectAttributeColumn<?> fromSchema(SchemaContext ctx,
|
||||||
AttributeSchema attributeSchema) {
|
AttributeSchema attributeSchema) {
|
||||||
String name = attributeSchema.getName();
|
String name = attributeSchema.getName();
|
||||||
Class<?> type = computeColumnType(ctx, attributeSchema);
|
Class<?> type = computeColumnType(ctx, attributeSchema);
|
||||||
return new AutoAttributeColumn(name, type);
|
return new AutoAttributeColumn<>(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AutoAttributeColumn(String attributeName, Class<?> attributeType) {
|
public AutoAttributeColumn(String attributeName, Class<T> attributeType) {
|
||||||
super(attributeName, attributeType);
|
super(attributeName, attributeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save and restore these between sessions, esp., their settings
|
// TODO: Save and restore these between sessions, esp., their settings
|
||||||
private Map<ColKey, TraceValueObjectAttributeColumn> columnCache = new HashMap<>();
|
private Map<ColKey, TraceValueObjectAttributeColumn<?>> columnCache = new HashMap<>();
|
||||||
|
|
||||||
protected ObjectTableModel(Plugin plugin) {
|
protected ObjectTableModel(Plugin plugin) {
|
||||||
super("Object Model", plugin);
|
super("Object Model", plugin);
|
||||||
|
@ -454,14 +538,14 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
int count = getColumnCount();
|
int count = getColumnCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
if (column instanceof TraceValueObjectAttributeColumn attrCol) {
|
if (column instanceof TraceValueObjectAttributeColumn<?> attrCol) {
|
||||||
attrCol.setDiffColor(diffColor);
|
attrCol.setDiffColor(diffColor);
|
||||||
}
|
}
|
||||||
else if (column instanceof TraceValueValColumn valCol) {
|
else if (column instanceof TraceValueValColumn valCol) {
|
||||||
valCol.setDiffColor(diffColor);
|
valCol.setDiffColor(diffColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
for (TraceValueObjectAttributeColumn<?> column : columnCache.values()) {
|
||||||
column.setDiffColor(diffColor);
|
column.setDiffColor(diffColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,14 +555,14 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
int count = getColumnCount();
|
int count = getColumnCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
if (column instanceof TraceValueObjectAttributeColumn attrCol) {
|
if (column instanceof TraceValueObjectAttributeColumn<?> attrCol) {
|
||||||
attrCol.setDiffColorSel(diffColorSel);
|
attrCol.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
else if (column instanceof TraceValueValColumn valCol) {
|
else if (column instanceof TraceValueValColumn valCol) {
|
||||||
valCol.setDiffColorSel(diffColorSel);
|
valCol.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
for (TraceValueObjectAttributeColumn<?> column : columnCache.values()) {
|
||||||
column.setDiffColorSel(diffColorSel);
|
column.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import ghidra.trace.model.Trace;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TraceValueLifePlotColumn
|
public class TraceValueLifePlotColumn
|
||||||
extends AbstractDynamicTableColumn<ValueRow, SpanSet<Long,?>, Trace> {
|
extends AbstractDynamicTableColumn<ValueRow, SpanSet<Long, ?>, Trace> {
|
||||||
|
|
||||||
private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>();
|
private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>();
|
||||||
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
|
@ -45,7 +45,7 @@ public class TraceValueLifePlotColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GColumnRenderer<SpanSet<Long,?>> getColumnRenderer() {
|
public GColumnRenderer<SpanSet<Long, ?>> getColumnRenderer() {
|
||||||
return cellRenderer;
|
return cellRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,18 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
import java.awt.Color;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
import java.awt.Component;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.swing.JTable;
|
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
|
||||||
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
|
|
||||||
import docking.widgets.table.sort.DefaultColumnComparator;
|
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
|
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||||
|
@ -37,49 +26,10 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||||
import ghidra.docking.settings.Settings;
|
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
|
||||||
|
|
||||||
public class TraceValueObjectAttributeColumn
|
public class TraceValueObjectAttributeColumn<T>
|
||||||
extends AbstractDynamicTableColumn<ValueRow, ValueRow, Trace> {
|
extends TraceValueObjectPropertyColumn<T> {
|
||||||
|
|
||||||
public class AttributeRenderer extends AbstractGColumnRenderer<ValueRow>
|
|
||||||
implements ColorsModified.InTable {
|
|
||||||
{
|
|
||||||
setHTMLRenderingEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFilterString(ValueRow t, Settings settings) {
|
|
||||||
return t.getAttributeDisplay(attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
|
||||||
super.getTableCellRendererComponent(data);
|
|
||||||
ValueRow row = (ValueRow) data.getValue();
|
|
||||||
setText(row.getAttributeHtmlDisplay(attributeName));
|
|
||||||
setToolTipText(row.getAttributeToolTip(attributeName));
|
|
||||||
setForeground(getForegroundFor(data.getTable(), row.isAttributeModified(attributeName),
|
|
||||||
data.isSelected()));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getDiffForeground(JTable table) {
|
|
||||||
return diffColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getDiffSelForeground(JTable table) {
|
|
||||||
return diffColorSel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) {
|
public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||||
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
|
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
|
||||||
|
@ -105,18 +55,11 @@ public class TraceValueObjectAttributeColumn
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String attributeName;
|
protected final String attributeName;
|
||||||
private final Class<?> attributeType;
|
|
||||||
private final AttributeRenderer renderer = new AttributeRenderer();
|
|
||||||
private final Comparator<ValueRow> comparator;
|
|
||||||
|
|
||||||
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
public TraceValueObjectAttributeColumn(String attributeName, Class<T> attributeType) {
|
||||||
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
super(attributeType);
|
||||||
|
|
||||||
public TraceValueObjectAttributeColumn(String attributeName, Class<?> attributeType) {
|
|
||||||
this.attributeName = attributeName;
|
this.attributeName = attributeName;
|
||||||
this.attributeType = attributeType;
|
|
||||||
this.comparator = newTypedComparator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,43 +74,7 @@ public class TraceValueObjectAttributeColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValueRow getValue(ValueRow rowObject, Settings settings, Trace data,
|
public ValueProperty<T> getProperty(ValueRow row) {
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
return row.getAttribute(attributeName, propertyType);
|
||||||
return rowObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GColumnRenderer<ValueRow> getColumnRenderer() {
|
|
||||||
return renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Comparator<ValueRow> getComparator(DynamicColumnTableModel<?> model, int columnIndex) {
|
|
||||||
return comparator == null ? null
|
|
||||||
: comparator.thenComparing(
|
|
||||||
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getAttributeValue(ValueRow row) {
|
|
||||||
TraceObjectValue edge = row.getAttribute(attributeName);
|
|
||||||
return edge == null ? null : edge.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <C extends Comparable<C>> Comparator<ValueRow> newTypedComparator() {
|
|
||||||
if (Comparable.class.isAssignableFrom(attributeType)) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<C> cls = (Class<C>) attributeType.asSubclass(Comparable.class);
|
|
||||||
Function<ValueRow, C> keyExtractor = r -> cls.cast(getAttributeValue(r));
|
|
||||||
return Comparator.comparing(keyExtractor, new DefaultColumnComparator());
|
|
||||||
}
|
|
||||||
return null; // Opt for the default filter-string-based comparator
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDiffColor(Color diffColor) {
|
|
||||||
this.diffColor = diffColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDiffColorSel(Color diffColorSel) {
|
|
||||||
this.diffColorSel = diffColorSel;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
|
||||||
|
import docking.widgets.checkbox.GCheckBox;
|
||||||
|
import docking.widgets.table.*;
|
||||||
|
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
|
||||||
|
import docking.widgets.table.sort.DefaultColumnComparator;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
|
public abstract class TraceValueObjectPropertyColumn<T>
|
||||||
|
extends AbstractDynamicTableColumn<ValueRow, ValueProperty<T>, Trace> {
|
||||||
|
|
||||||
|
public class PropertyRenderer extends AbstractGColumnRenderer<ValueProperty<T>>
|
||||||
|
implements ColorsModified.InTable {
|
||||||
|
{
|
||||||
|
setHTMLRenderingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterString(ValueProperty<T> p, Settings settings) {
|
||||||
|
return p.getDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ValueProperty<T> p = (ValueProperty<T>) data.getValue();
|
||||||
|
setText(p.getHtmlDisplay());
|
||||||
|
setToolTipText(p.getToolTip());
|
||||||
|
|
||||||
|
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getDiffForeground(JTable p) {
|
||||||
|
return diffColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getDiffSelForeground(JTable p) {
|
||||||
|
return diffColorSel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BooleanPropertyRenderer extends PropertyRenderer {
|
||||||
|
protected GCheckBox cb;
|
||||||
|
{
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
cb = new GCheckBox();
|
||||||
|
cb.setHorizontalAlignment(CENTER);
|
||||||
|
cb.setOpaque(false);
|
||||||
|
add(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ValueProperty<T> property = (ValueProperty<T>) data.getValue();
|
||||||
|
T value = property.getValue();
|
||||||
|
if (value instanceof Boolean b) {
|
||||||
|
cb.setVisible(true);
|
||||||
|
cb.setSelected(b);
|
||||||
|
setText("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cb.setVisible(false);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate() {
|
||||||
|
synchronized (getTreeLock()) {
|
||||||
|
validateTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Class<T> propertyType;
|
||||||
|
private final GColumnRenderer<ValueProperty<T>> renderer;
|
||||||
|
private final Comparator<ValueProperty<T>> comparator;
|
||||||
|
|
||||||
|
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
||||||
|
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
||||||
|
|
||||||
|
public TraceValueObjectPropertyColumn(Class<T> propertyType) {
|
||||||
|
this.propertyType = propertyType;
|
||||||
|
this.comparator = newTypedComparator();
|
||||||
|
this.renderer = createRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GColumnRenderer<ValueProperty<T>> createRenderer() {
|
||||||
|
if (propertyType == Boolean.class) {
|
||||||
|
return new BooleanPropertyRenderer();
|
||||||
|
}
|
||||||
|
return new PropertyRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<ValueProperty<T>> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comparator<ValueProperty<T>> getComparator(DynamicColumnTableModel<?> model,
|
||||||
|
int columnIndex) {
|
||||||
|
return comparator == null ? null
|
||||||
|
: comparator.thenComparing(
|
||||||
|
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ValueProperty<T> getProperty(ValueRow row);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueProperty<T> getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
return getProperty(rowObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <C extends Comparable<C>> Comparator<ValueProperty<T>> newTypedComparator() {
|
||||||
|
if (Comparable.class.isAssignableFrom(propertyType)) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<C> cls = (Class<C>) propertyType.asSubclass(Comparable.class);
|
||||||
|
Function<ValueProperty<T>, C> keyExtractor = r -> cls.cast(r.getValue());
|
||||||
|
return Comparator.comparing(keyExtractor, new DefaultColumnComparator());
|
||||||
|
}
|
||||||
|
return null; // Opt for the default filter-string-based comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiffColor(Color diffColor) {
|
||||||
|
this.diffColor = diffColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiffColorSel(Color diffColorSel) {
|
||||||
|
this.diffColorSel = diffColorSel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,17 +18,20 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import docking.ActionContext;
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
import docking.widgets.table.TableColumnDescriptor;
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
|
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
|
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
|
||||||
|
import ghidra.app.services.DebuggerListingService;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.dbg.target.TargetStack;
|
import ghidra.dbg.target.TargetStack;
|
||||||
import ghidra.dbg.target.TargetStackFrame;
|
import ghidra.dbg.target.TargetStackFrame;
|
||||||
|
@ -40,11 +43,13 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.listing.Function;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import utilities.util.SuppressableCallback;
|
import utilities.util.SuppressableCallback;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelectionListener {
|
public class DebuggerStackPanel extends ObjectsTablePanel
|
||||||
|
implements ListSelectionListener, CellActivationListener {
|
||||||
|
|
||||||
private static class FrameLevelColumn extends TraceValueKeyColumn {
|
private static class FrameLevelColumn extends TraceValueKeyColumn {
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,7 +63,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FramePcColumn extends TraceValueObjectAttributeColumn {
|
private static class FramePcColumn extends TraceValueObjectAttributeColumn<Address> {
|
||||||
public FramePcColumn() {
|
public FramePcColumn() {
|
||||||
super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class);
|
super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +85,8 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
@Override
|
@Override
|
||||||
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
TraceObjectValue value = rowObject.getAttribute(TargetStackFrame.PC_ATTRIBUTE_NAME);
|
TraceObjectValue value =
|
||||||
|
rowObject.getAttributeEntry(TargetStackFrame.PC_ATTRIBUTE_NAME);
|
||||||
return value == null ? null : provider.getFunction((Address) value.getValue());
|
return value == null ? null : provider.getFunction((Address) value.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,13 +102,14 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
descriptor.addVisibleColumn(new FrameLevelColumn());
|
descriptor.addVisibleColumn(new FrameLevelColumn());
|
||||||
descriptor.addVisibleColumn(new FramePcColumn());
|
descriptor.addVisibleColumn(new FramePcColumn());
|
||||||
descriptor.addVisibleColumn(new FrameFunctionColumn());
|
descriptor.addVisibleColumn(new FrameFunctionColumn());
|
||||||
// TODO: Comment column?
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DebuggerStackProvider provider;
|
private final DebuggerStackProvider provider;
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
protected DebuggerListingService listingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected DebuggerTraceManagerService traceManager;
|
protected DebuggerTraceManagerService traceManager;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
@ -122,6 +129,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
setShowHidden(false);
|
setShowHidden(false);
|
||||||
|
|
||||||
addSelectionListener(this);
|
addSelectionListener(this);
|
||||||
|
addCellActivationListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,7 +137,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
return new StackTableModel(plugin);
|
return new StackTableModel(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionContext getActionContext() {
|
public DebuggerObjectActionContext getActionContext() {
|
||||||
return myActionContext;
|
return myActionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +145,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return ModelQuery.EMPTY;
|
return ModelQuery.EMPTY;
|
||||||
}
|
}
|
||||||
TargetObjectSchema rootSchema = object.getRoot()
|
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
|
||||||
.getTargetSchema();
|
|
||||||
List<String> stackPath = rootSchema
|
List<String> stackPath = rootSchema
|
||||||
.searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList());
|
.searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList());
|
||||||
if (stackPath == null) {
|
if (stackPath == null) {
|
||||||
|
@ -176,4 +183,21 @@ public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelecti
|
||||||
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
|
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cellActivated(JTable table) {
|
||||||
|
if (listingService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int row = table.getSelectedRow();
|
||||||
|
int col = table.getSelectedColumn();
|
||||||
|
Object value = table.getValueAt(row, col);
|
||||||
|
if (!(value instanceof ValueProperty<?> property)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object propVal = property.getValue();
|
||||||
|
if (propVal instanceof Address address) {
|
||||||
|
listingService.goTo(address, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,6 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.address.Address;
|
|
||||||
import ghidra.program.model.listing.Function;
|
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.trace.model.*;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Debugger stack view",
|
shortDescription = "Debugger stack view",
|
||||||
|
|
|
@ -110,10 +110,13 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
|
final ActionContext context;
|
||||||
if (isLegacy(current.getTrace())) {
|
if (isLegacy(current.getTrace())) {
|
||||||
return legacyPanel.getActionContext();
|
context = legacyPanel.getActionContext();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context = panel.getActionContext();
|
||||||
}
|
}
|
||||||
ActionContext context = panel.getActionContext();
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,7 +255,7 @@ public enum DebuggerStaticMappingProposals {
|
||||||
protected static Set<String> getLikelyModulesFromName(TraceMemoryRegion region) {
|
protected static Set<String> getLikelyModulesFromName(TraceMemoryRegion region) {
|
||||||
String key;
|
String key;
|
||||||
try {
|
try {
|
||||||
List<String> path = PathUtils.parse(region.getName());
|
List<String> path = PathUtils.parse(region.getPath());
|
||||||
key = PathUtils.getKey(path);
|
key = PathUtils.getKey(path);
|
||||||
if (PathUtils.isIndex(key)) {
|
if (PathUtils.isIndex(key)) {
|
||||||
key = PathUtils.parseIndex(key);
|
key = PathUtils.parseIndex(key);
|
||||||
|
|
|
@ -40,11 +40,14 @@ import org.junit.runner.Description;
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.ActionContextProvider;
|
import docking.action.ActionContextProvider;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.nav.Navigatable;
|
import ghidra.app.nav.Navigatable;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectPropertyColumn;
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.*;
|
||||||
import ghidra.app.plugin.core.debug.service.model.*;
|
import ghidra.app.plugin.core.debug.service.model.*;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
|
@ -54,6 +57,7 @@ import ghidra.dbg.model.AbstractTestTargetRegisterBank;
|
||||||
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||||
|
import ghidra.docking.settings.SettingsImpl;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.database.ProgramDB;
|
import ghidra.program.database.ProgramDB;
|
||||||
|
@ -486,6 +490,19 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
runSwing(() -> nav.setSelection(sel));
|
runSwing(() -> nav.setSelection(sel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Object rowColVal(ValueRow row, DynamicTableColumn<ValueRow, ?, Trace> col) {
|
||||||
|
if (col instanceof TraceValueObjectPropertyColumn<?> attrCol) {
|
||||||
|
return attrCol.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool).getValue();
|
||||||
|
}
|
||||||
|
Object value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> String rowColDisplay(ValueRow row, DynamicTableColumn<ValueRow, T, Trace> col) {
|
||||||
|
T value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
|
||||||
|
return col.getColumnRenderer().getFilterString(value, SettingsImpl.NO_SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
||||||
return LocationTrackingSpecFactory.fromConfigName(name);
|
return LocationTrackingSpecFactory.fromConfigName(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,391 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
|
import generic.test.category.NightlyCategory;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.memory.DebuggerLegacyRegionsPanel.RegionTableColumns;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
|
||||||
|
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
import ghidra.program.model.mem.Memory;
|
||||||
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.memory.*;
|
||||||
|
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
|
@Category(NightlyCategory.class)
|
||||||
|
public class DebuggerRegionsProviderLegacyTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
||||||
|
DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
|
protected TraceMemoryRegion regionExeText;
|
||||||
|
protected TraceMemoryRegion regionExeData;
|
||||||
|
protected TraceMemoryRegion regionLibText;
|
||||||
|
protected TraceMemoryRegion regionLibData;
|
||||||
|
|
||||||
|
protected MemoryBlock blockExeText;
|
||||||
|
protected MemoryBlock blockExeData;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpRegionsTest() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerRegionsPlugin.class);
|
||||||
|
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRegions() throws Exception {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0,
|
||||||
|
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0,
|
||||||
|
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
||||||
|
regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0,
|
||||||
|
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0,
|
||||||
|
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addBlocks() throws Exception {
|
||||||
|
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
|
||||||
|
Memory mem = program.getMemory();
|
||||||
|
blockExeText = mem.createInitializedBlock(".text", tb.addr(0x00400000), 0x100, (byte) 0,
|
||||||
|
monitor, false);
|
||||||
|
blockExeData = mem.createInitializedBlock(".data", tb.addr(0x00600000), 0x80, (byte) 0,
|
||||||
|
monitor, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoTraceEmpty() throws Exception {
|
||||||
|
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateEmptyTraceEmpty() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddThenActivateTracePopulates() throws Exception {
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
assertEquals("Memory[bin:.text]", row.getName());
|
||||||
|
assertEquals(tb.addr(0x00400000), row.getMinAddress());
|
||||||
|
assertEquals(tb.addr(0x0040ffff), row.getMaxAddress());
|
||||||
|
assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange());
|
||||||
|
assertEquals(0x10000, row.getLength());
|
||||||
|
assertEquals(0L, row.getCreatedSnap());
|
||||||
|
assertEquals("", row.getDestroyedSnap());
|
||||||
|
assertEquals(Lifespan.nowOn(0), row.getLifespan());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateTraceThenAddPopulates() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteRemoves() throws Exception {
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
region.delete();
|
||||||
|
}
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUndoRedo() throws Exception {
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
|
||||||
|
undo(tb.trace);
|
||||||
|
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
|
||||||
|
|
||||||
|
redo(tb.trace);
|
||||||
|
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAbort() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
|
||||||
|
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
tid.abort();
|
||||||
|
}
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
assertEquals(0, provider.legacyPanel.regionTableModel.getModelData().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClickNavigates() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
|
||||||
|
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.START.ordinal(), 2);
|
||||||
|
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
|
||||||
|
|
||||||
|
clickTableCell(provider.legacyPanel.regionTable, 0, RegionTableColumns.END.ordinal(), 2);
|
||||||
|
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionMapRegions() throws Exception {
|
||||||
|
assertFalse(provider.actionMapRegions.isEnabled());
|
||||||
|
|
||||||
|
createAndOpenTrace();
|
||||||
|
createAndOpenProgramFromTrace();
|
||||||
|
intoProject(tb.trace);
|
||||||
|
intoProject(program);
|
||||||
|
|
||||||
|
addRegions();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
// Still
|
||||||
|
assertFalse(provider.actionMapRegions.isEnabled());
|
||||||
|
|
||||||
|
addBlocks();
|
||||||
|
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||||
|
program.setName("echo");
|
||||||
|
}
|
||||||
|
waitForDomainObject(program);
|
||||||
|
waitForPass(() -> assertEquals(4, provider.legacyPanel.regionTable.getRowCount()));
|
||||||
|
|
||||||
|
// NB. Feature works "best" when all regions of modules are selected
|
||||||
|
// TODO: Test cases where feature works "worst"?
|
||||||
|
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
|
||||||
|
waitForSwing();
|
||||||
|
performEnabledAction(provider, provider.actionMapRegions, false);
|
||||||
|
|
||||||
|
DebuggerRegionMapProposalDialog propDialog =
|
||||||
|
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
|
||||||
|
|
||||||
|
List<RegionMapEntry> proposal = new ArrayList<>(propDialog.getTableModel().getModelData());
|
||||||
|
assertEquals(2, proposal.size());
|
||||||
|
RegionMapEntry entry;
|
||||||
|
|
||||||
|
// Table sorts by name by default.
|
||||||
|
// Names are file name followed by min address, so .text is first.
|
||||||
|
entry = proposal.get(0);
|
||||||
|
assertEquals(regionExeText, entry.getRegion());
|
||||||
|
assertEquals(blockExeText, entry.getBlock());
|
||||||
|
entry = proposal.get(1);
|
||||||
|
assertEquals(regionExeData, entry.getRegion());
|
||||||
|
assertEquals(blockExeData, entry.getBlock());
|
||||||
|
|
||||||
|
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||||
|
|
||||||
|
DebuggerBlockChooserDialog blockDialog =
|
||||||
|
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||||
|
MemoryBlockRow row = blockDialog.getTableFilterPanel().getSelectedItem();
|
||||||
|
assertEquals(blockExeText, row.getBlock());
|
||||||
|
|
||||||
|
pressButtonByText(blockDialog, "OK", true);
|
||||||
|
assertEquals(blockExeData, entry.getBlock()); // Unchanged
|
||||||
|
// TODO: Test the changed case
|
||||||
|
|
||||||
|
Collection<? extends TraceStaticMapping> mappings =
|
||||||
|
tb.trace.getStaticMappingManager().getAllEntries();
|
||||||
|
assertEquals(0, mappings.size());
|
||||||
|
|
||||||
|
pressButtonByText(propDialog, "OK", true);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
assertEquals(2, mappings.size());
|
||||||
|
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
|
||||||
|
TraceStaticMapping sm;
|
||||||
|
|
||||||
|
sm = mit.next();
|
||||||
|
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||||
|
assertEquals("ram:00400000", sm.getStaticAddress());
|
||||||
|
assertEquals(0x100, sm.getLength());
|
||||||
|
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||||
|
|
||||||
|
sm = mit.next();
|
||||||
|
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||||
|
assertEquals("ram:00600000", sm.getStaticAddress());
|
||||||
|
assertEquals(0x80, sm.getLength());
|
||||||
|
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
|
||||||
|
|
||||||
|
assertFalse(mit.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: testActionMapRegionsTo
|
||||||
|
// TODO: testActionMapRegionTo
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionSelectAddresses() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
|
||||||
|
|
||||||
|
provider.setSelectedRegions(Set.of(region));
|
||||||
|
waitForSwing();
|
||||||
|
performEnabledAction(provider, provider.actionSelectAddresses, true);
|
||||||
|
|
||||||
|
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
|
||||||
|
new AddressSet(listing.getSelection())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionSelectRows() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
TraceMemoryRegion region;
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
||||||
|
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
||||||
|
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
RegionRow row = Unique.assertOne(provider.legacyPanel.regionTableModel.getModelData());
|
||||||
|
// NB. Table is debounced
|
||||||
|
waitForPass(() -> assertEquals(1, provider.legacyPanel.regionTable.getRowCount()));
|
||||||
|
assertEquals(region, row.getRegion());
|
||||||
|
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
|
||||||
|
|
||||||
|
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
|
||||||
|
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
|
||||||
|
new AddressSet(listing.getSelection())));
|
||||||
|
|
||||||
|
waitForSwing();
|
||||||
|
performEnabledAction(listing, provider.actionSelectRows, true);
|
||||||
|
|
||||||
|
waitForPass(
|
||||||
|
() -> assertEquals(Set.of(row), Set.copyOf(provider.legacyPanel.getSelectedRows())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.debug.gui.memory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.junit.experimental.categories.Category;
|
|
||||||
|
|
||||||
import generic.test.category.NightlyCategory;
|
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
|
||||||
import ghidra.util.database.UndoableTransaction;
|
|
||||||
|
|
||||||
@Category(NightlyCategory.class)
|
|
||||||
public class DebuggerRegionsProviderObjectTest extends DebuggerRegionsProviderTest {
|
|
||||||
|
|
||||||
protected SchemaContext ctx;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void createTrace(String langID) throws IOException {
|
|
||||||
super.createTrace(langID);
|
|
||||||
try {
|
|
||||||
activateObjectsMode();
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void activateObjectsMode() throws Exception {
|
|
||||||
ctx = XmlSchemaContext.deserialize("" + //
|
|
||||||
"<context>" + //
|
|
||||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
|
||||||
" <attribute name='Memory' schema='RegionContainer' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + //
|
|
||||||
" attributeResync='ONCE'>" + //
|
|
||||||
" <element schema='Region' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + //
|
|
||||||
" <interface name='MemoryRegion' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
"</context>");
|
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,61 +17,118 @@ package ghidra.app.plugin.core.debug.gui.memory;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.*;
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
import generic.Unique;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
|
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
|
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionMapProposalDialog.RegionMapTableColumns;
|
||||||
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsProvider.RegionTableColumns;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||||
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
import ghidra.app.services.RegionMapProposal.RegionMapEntry;
|
||||||
import ghidra.program.model.address.AddressSet;
|
import ghidra.dbg.target.TargetMemoryRegion;
|
||||||
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.model.mem.MemoryBlock;
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
|
||||||
import ghidra.trace.model.modules.TraceStaticMapping;
|
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||||
|
import ghidra.trace.model.target.*;
|
||||||
|
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import ghidra.util.table.GhidraTable;
|
||||||
|
|
||||||
@Category(NightlyCategory.class)
|
@Category(NightlyCategory.class)
|
||||||
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
||||||
DebuggerRegionsProvider provider;
|
DebuggerRegionsProvider provider;
|
||||||
|
|
||||||
protected TraceMemoryRegion regionExeText;
|
TraceObjectMemoryRegion regionExeText;
|
||||||
protected TraceMemoryRegion regionExeData;
|
TraceObjectMemoryRegion regionExeData;
|
||||||
protected TraceMemoryRegion regionLibText;
|
TraceObjectMemoryRegion regionLibText;
|
||||||
protected TraceMemoryRegion regionLibData;
|
TraceObjectMemoryRegion regionLibData;
|
||||||
|
|
||||||
protected MemoryBlock blockExeText;
|
MemoryBlock blockExeText;
|
||||||
protected MemoryBlock blockExeData;
|
MemoryBlock blockExeData;
|
||||||
|
|
||||||
@Before
|
protected SchemaContext ctx;
|
||||||
public void setUpRegionsTest() throws Exception {
|
|
||||||
addPlugin(tool, DebuggerRegionsPlugin.class);
|
@Override
|
||||||
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
|
protected void createTrace(String langID) throws IOException {
|
||||||
|
super.createTrace(langID);
|
||||||
|
try {
|
||||||
|
activateObjectsMode();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void activateObjectsMode() throws Exception {
|
||||||
|
ctx = XmlSchemaContext.deserialize("""
|
||||||
|
<context>
|
||||||
|
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
|
<attribute name='Memory' schema='RegionContainer' />
|
||||||
|
</schema>
|
||||||
|
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
|
||||||
|
attributeResync='ONCE'>
|
||||||
|
<element schema='Region' />
|
||||||
|
</schema>
|
||||||
|
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
|
<interface name='MemoryRegion' />
|
||||||
|
<attribute name='_display' schema='STRING' hidden='yes' />
|
||||||
|
<attribute name='_range' schema='RANGE' hidden='yes' />
|
||||||
|
<attribute name='_readable' schema='BOOL' hidden='yes' />
|
||||||
|
<attribute name='_writable' schema='BOOL' hidden='yes' />
|
||||||
|
<attribute name='_executable' schema='BOOL' hidden='yes' />
|
||||||
|
</schema>
|
||||||
|
</context>
|
||||||
|
""");
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObjectMemoryRegion addRegion(String name, long loaded, AddressRange range) {
|
||||||
|
boolean isData = name.endsWith(".data");
|
||||||
|
TraceObjectManager om = tb.trace.getObjectManager();
|
||||||
|
TraceObjectKeyPath memPath = TraceObjectKeyPath.parse("Memory");
|
||||||
|
Lifespan span = Lifespan.nowOn(loaded);
|
||||||
|
TraceObjectMemoryRegion region = Objects.requireNonNull(om.createObject(memPath.index(name))
|
||||||
|
.insert(span, ConflictResolution.TRUNCATE)
|
||||||
|
.getDestination(null)
|
||||||
|
.queryInterface(TraceObjectMemoryRegion.class));
|
||||||
|
TraceObject obj = region.getObject();
|
||||||
|
obj.setAttribute(span, TargetMemoryRegion.DISPLAY_ATTRIBUTE_NAME, name);
|
||||||
|
obj.setAttribute(span, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, range);
|
||||||
|
obj.setAttribute(span, TargetMemoryRegion.READABLE_ATTRIBUTE_NAME, true);
|
||||||
|
obj.setAttribute(span, TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME, isData);
|
||||||
|
obj.setAttribute(span, TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME, !isData);
|
||||||
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addRegions() throws Exception {
|
protected void addRegions() throws Exception {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0,
|
regionExeText = addRegion("/bin/echo .text", 0, tb.range(0x55550000, 0x555500ff));
|
||||||
tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
regionExeData = addRegion("/bin/echo .data", 0, tb.range(0x55750000, 0x5575007f));
|
||||||
regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0,
|
regionLibText = addRegion("/lib/libc.so .text", 0, tb.range(0x7f000000, 0x7f0003ff));
|
||||||
tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
regionLibData = addRegion("/lib/libc.so .data", 0, tb.range(0x7f100000, 0x7f10003f));
|
||||||
regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0,
|
|
||||||
tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0,
|
|
||||||
tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,45 +142,83 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void assertTableSize(int size) {
|
||||||
|
assertEquals(size, provider.panel.getAllItems().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRow(int position, Object object, String name, Address start,
|
||||||
|
Address end, long length, String flags) {
|
||||||
|
ValueRow row = provider.panel.getAllItems().get(position);
|
||||||
|
DynamicTableColumn<ValueRow, ValueRow, Trace> nameCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Name", ValueRow.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> startCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> endCol =
|
||||||
|
provider.panel.getColumnByNameAndType("End", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> lengthCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Length", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> readCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Read", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> writeCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Write", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> executeCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Execute", ValueProperty.class).getValue();
|
||||||
|
|
||||||
|
assertSame(object, row.getValue().getValue());
|
||||||
|
assertEquals(name, rowColDisplay(row, nameCol));
|
||||||
|
assertEquals(start, rowColVal(row, startCol));
|
||||||
|
assertEquals(end, rowColVal(row, endCol));
|
||||||
|
assertEquals(length, rowColVal(row, lengthCol));
|
||||||
|
assertEquals(flags.contains("r"), rowColVal(row, readCol));
|
||||||
|
assertEquals(flags.contains("w"), rowColVal(row, writeCol));
|
||||||
|
assertEquals(flags.contains("x"), rowColVal(row, executeCol));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpRegionsProviderTest() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerRegionsPlugin.class);
|
||||||
|
provider = waitForComponentProvider(DebuggerRegionsProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void testDownRegionsProviderTest() throws Exception {
|
||||||
|
traceManager.activate(DebuggerCoordinates.NOWHERE);
|
||||||
|
waitForSwing();
|
||||||
|
waitForTasks();
|
||||||
|
runSwing(() -> traceManager.closeAllTraces());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoTraceEmpty() throws Exception {
|
public void testNoTraceEmpty() throws Exception {
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
waitForPass(() -> assertTableSize(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateEmptyTraceEmpty() throws Exception {
|
public void testActivateEmptyTraceEmpty() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
waitForPass(() -> assertTableSize(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddThenActivateTracePopulates() throws Exception {
|
public void testAddThenActivateTracePopulates() throws Exception {
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForPass(() -> {
|
||||||
assertEquals(region, row.getRegion());
|
assertTableSize(1);
|
||||||
assertEquals("Memory[bin:.text]", row.getName());
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
assertEquals(tb.addr(0x00400000), row.getMinAddress());
|
0x10000, "rx");
|
||||||
assertEquals(tb.addr(0x0040ffff), row.getMaxAddress());
|
});
|
||||||
assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange());
|
|
||||||
assertEquals(0x10000, row.getLength());
|
|
||||||
assertEquals(0L, row.getCreatedSnap());
|
|
||||||
assertEquals("", row.getDestroyedSnap());
|
|
||||||
assertEquals(Lifespan.nowOn(0), row.getLifespan());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -131,65 +226,81 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
waitForPass(() -> assertTableSize(0));
|
||||||
|
|
||||||
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
waitForSwing();
|
waitForPass(() -> {
|
||||||
|
assertTableSize(1);
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
assertEquals(region, row.getRegion());
|
0x10000, "rx");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteRemoves() throws Exception {
|
public void testRemoveRegionRemovesFromTable() throws Exception {
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForPass(() -> {
|
||||||
assertEquals(region, row.getRegion());
|
assertTableSize(1);
|
||||||
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
|
0x10000, "rx");
|
||||||
|
});
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
region.delete();
|
region.getObject().removeTree(Lifespan.nowOn(0));
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
waitForPass(() -> assertTableSize(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUndoRedo() throws Exception {
|
public void testUndoRedo() throws Exception {
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
|
|
||||||
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
Unique.assertOne(provider.regionTableModel.getModelData());
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertTableSize(1);
|
||||||
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
|
0x10000, "rx");
|
||||||
|
});
|
||||||
|
|
||||||
undo(tb.trace);
|
undo(tb.trace);
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
waitForPass(() -> assertTableSize(0));
|
||||||
|
|
||||||
redo(tb.trace);
|
redo(tb.trace);
|
||||||
Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertTableSize(1);
|
||||||
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
|
0x10000, "rx");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -197,17 +308,23 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
|
|
||||||
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
|
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForTasks();
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertTableSize(1);
|
||||||
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
|
||||||
|
tb.addr(0x0040ffff), 0x10000, "rx");
|
||||||
|
});
|
||||||
tid.abort();
|
tid.abort();
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
assertEquals(0, provider.regionTableModel.getModelData().size());
|
waitForTasks();
|
||||||
|
|
||||||
|
waitForPass(() -> assertTableSize(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -215,27 +332,31 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
addPlugin(tool, DebuggerListingPlugin.class);
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount()));
|
waitForPass(() -> {
|
||||||
|
assertTableSize(1);
|
||||||
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000), tb.addr(0x0040ffff),
|
||||||
|
0x10000, "rx");
|
||||||
|
});
|
||||||
|
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
|
||||||
|
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
int startColIdx =
|
||||||
assertEquals(region, row.getRegion());
|
provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getKey();
|
||||||
|
int endColIdx = provider.panel.getColumnByNameAndType("End", ValueProperty.class).getKey();
|
||||||
|
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
||||||
|
|
||||||
clickTableCell(provider.regionTable, 0, RegionTableColumns.START.ordinal(), 2);
|
clickTableCell(table, 0, startColIdx, 2);
|
||||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
|
waitForPass(() -> assertEquals(tb.addr(0x00400000), listing.getLocation().getAddress()));
|
||||||
|
|
||||||
clickTableCell(provider.regionTable, 0, RegionTableColumns.END.ordinal(), 2);
|
clickTableCell(table, 0, endColIdx, 2);
|
||||||
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
|
waitForPass(() -> assertEquals(tb.addr(0x0040ffff), listing.getLocation().getAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,15 +381,13 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
program.setName("echo");
|
program.setName("echo");
|
||||||
}
|
}
|
||||||
waitForDomainObject(program);
|
waitForDomainObject(program);
|
||||||
waitForPass(() -> assertEquals(4, provider.regionTable.getRowCount()));
|
waitForPass(() -> assertTableSize(4));
|
||||||
|
|
||||||
// NB. Feature works "best" when all regions of modules are selected
|
// NB. Feature works "best" when all regions of modules are selected
|
||||||
// TODO: Test cases where feature works "worst"?
|
// TODO: Test cases where feature works "worst"?
|
||||||
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
|
provider.setSelectedRegions(Set.of(regionExeText, regionExeData));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertTrue(provider.actionMapRegions.isEnabled());
|
performEnabledAction(provider, provider.actionMapRegions, false);
|
||||||
|
|
||||||
performAction(provider.actionMapRegions, false);
|
|
||||||
|
|
||||||
DebuggerRegionMapProposalDialog propDialog =
|
DebuggerRegionMapProposalDialog propDialog =
|
||||||
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
|
waitForDialogComponent(DebuggerRegionMapProposalDialog.class);
|
||||||
|
@ -277,16 +396,16 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(2, proposal.size());
|
assertEquals(2, proposal.size());
|
||||||
RegionMapEntry entry;
|
RegionMapEntry entry;
|
||||||
|
|
||||||
// Table sorts by name by default.
|
// Table sorts by name by default, so .data is first
|
||||||
// Names are file name followed by min address, so .text is first.
|
|
||||||
entry = proposal.get(0);
|
entry = proposal.get(0);
|
||||||
assertEquals(regionExeText, entry.getRegion());
|
|
||||||
assertEquals(blockExeText, entry.getBlock());
|
|
||||||
entry = proposal.get(1);
|
|
||||||
assertEquals(regionExeData, entry.getRegion());
|
assertEquals(regionExeData, entry.getRegion());
|
||||||
assertEquals(blockExeData, entry.getBlock());
|
assertEquals(blockExeData, entry.getBlock());
|
||||||
|
entry = proposal.get(1);
|
||||||
|
assertEquals(regionExeText, entry.getRegion());
|
||||||
|
assertEquals(blockExeText, entry.getBlock());
|
||||||
|
|
||||||
clickTableCell(propDialog.getTable(), 0, RegionMapTableColumns.CHOOSE.ordinal(), 1);
|
// Select the .text row
|
||||||
|
clickTableCell(propDialog.getTable(), 1, RegionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||||
|
|
||||||
DebuggerBlockChooserDialog blockDialog =
|
DebuggerBlockChooserDialog blockDialog =
|
||||||
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||||
|
@ -294,7 +413,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(blockExeText, row.getBlock());
|
assertEquals(blockExeText, row.getBlock());
|
||||||
|
|
||||||
pressButtonByText(blockDialog, "OK", true);
|
pressButtonByText(blockDialog, "OK", true);
|
||||||
assertEquals(blockExeData, entry.getBlock()); // Unchanged
|
assertEquals(blockExeText, entry.getBlock()); // Unchanged
|
||||||
// TODO: Test the changed case
|
// TODO: Test the changed case
|
||||||
|
|
||||||
Collection<? extends TraceStaticMapping> mappings =
|
Collection<? extends TraceStaticMapping> mappings =
|
||||||
|
@ -304,54 +423,50 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
pressButtonByText(propDialog, "OK", true);
|
pressButtonByText(propDialog, "OK", true);
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
assertEquals(2, mappings.size());
|
assertEquals(2, mappings.size());
|
||||||
|
// Ordered by db key. Thus, in order added
|
||||||
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
|
Iterator<? extends TraceStaticMapping> mit = mappings.iterator();
|
||||||
TraceStaticMapping sm;
|
TraceStaticMapping sm;
|
||||||
|
|
||||||
sm = mit.next();
|
|
||||||
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
|
||||||
assertEquals("ram:00400000", sm.getStaticAddress());
|
|
||||||
assertEquals(0x100, sm.getLength());
|
|
||||||
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
|
||||||
|
|
||||||
sm = mit.next();
|
sm = mit.next();
|
||||||
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||||
assertEquals("ram:00600000", sm.getStaticAddress());
|
assertEquals("ram:00600000", sm.getStaticAddress());
|
||||||
assertEquals(0x80, sm.getLength());
|
assertEquals(0x80, sm.getLength());
|
||||||
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
|
assertEquals(tb.addr(0x55750000), sm.getMinTraceAddress());
|
||||||
|
|
||||||
|
sm = mit.next();
|
||||||
|
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||||
|
assertEquals("ram:00400000", sm.getStaticAddress());
|
||||||
|
assertEquals(0x100, sm.getLength());
|
||||||
|
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||||
|
|
||||||
assertFalse(mit.hasNext());
|
assertFalse(mit.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: testActionMapRegionsTo
|
|
||||||
// TODO: testActionMapRegionTo
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionSelectAddresses() throws Exception {
|
public void testActionSelectAddresses() throws Exception {
|
||||||
addPlugin(tool, DebuggerListingPlugin.class);
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForPass(() -> {
|
||||||
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount()));
|
assertTableSize(1);
|
||||||
assertEquals(region, row.getRegion());
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
|
||||||
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
|
tb.addr(0x0040ffff), 0x10000, "rx");
|
||||||
|
});
|
||||||
|
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
|
||||||
|
|
||||||
provider.setSelectedRegions(Set.of(region));
|
provider.setSelectedRegions(Set.of(region));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertTrue(provider.actionSelectAddresses.isEnabled());
|
performEnabledAction(provider, provider.actionSelectAddresses, true);
|
||||||
performAction(provider.actionSelectAddresses);
|
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
|
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
|
||||||
new AddressSet(listing.getSelection())));
|
new AddressSet(listing.getSelection())));
|
||||||
|
@ -362,33 +477,33 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
addPlugin(tool, DebuggerListingPlugin.class);
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
|
|
||||||
createTrace();
|
createAndOpenTrace();
|
||||||
|
|
||||||
TraceMemoryRegion region;
|
TraceObjectMemoryRegion region;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceMemoryManager mm = tb.trace.getMemoryManager();
|
region = addRegion("bin:.text", 0, tb.range(0x00400000, 0x0040ffff));
|
||||||
region = mm.addRegion("Memory[bin:.text]", Lifespan.nowOn(0),
|
|
||||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
|
waitForPass(() -> {
|
||||||
// NB. Table is debounced
|
assertTableSize(1);
|
||||||
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount()));
|
assertRow(0, region.getObject(), "bin:.text", tb.addr(0x00400000),
|
||||||
assertEquals(region, row.getRegion());
|
tb.addr(0x0040ffff), 0x10000, "rx");
|
||||||
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
|
});
|
||||||
|
waitForPass(() -> assertFalse(tb.trace.getProgramView().getMemory().isEmpty()));
|
||||||
|
|
||||||
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
|
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
|
||||||
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
|
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
|
||||||
new AddressSet(listing.getSelection())));
|
new AddressSet(listing.getSelection())));
|
||||||
|
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertTrue(provider.actionSelectRows.isEnabled());
|
performEnabledAction(listing, provider.actionSelectRows, true);
|
||||||
performAction(provider.actionSelectRows);
|
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(Set.of(row), Set.copyOf(provider.getSelectedRows())));
|
waitForPass(() -> {
|
||||||
|
List<ValueRow> allItems = provider.panel.getAllItems();
|
||||||
|
assertEquals(Set.of(allItems.get(0)), Set.copyOf(provider.panel.getSelectedItems()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,8 @@ import org.junit.*;
|
||||||
import docking.widgets.table.DynamicTableColumn;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
|
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||||
import ghidra.app.services.DebuggerStaticMappingService;
|
import ghidra.app.services.DebuggerStaticMappingService;
|
||||||
|
@ -39,7 +39,6 @@ import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
import ghidra.dbg.util.PathPattern;
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
import ghidra.docking.settings.SettingsImpl;
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSet;
|
import ghidra.program.model.address.AddressSet;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
|
@ -214,23 +213,15 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
||||||
assertEquals(size, stackProvider.panel.getAllItems().size());
|
assertEquals(size, stackProvider.panel.getAllItems().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object rowColVal(ValueRow row, DynamicTableColumn<ValueRow, ?, Trace> col) {
|
|
||||||
if (col instanceof TraceValueObjectAttributeColumn attrCol) {
|
|
||||||
return attrCol.getAttributeValue(row);
|
|
||||||
}
|
|
||||||
Object value = col.getValue(row, SettingsImpl.NO_SETTINGS, tb.trace, tool);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertRow(int level, Address pcVal, Function func) {
|
protected void assertRow(int level, Address pcVal, Function func) {
|
||||||
ValueRow row = stackProvider.panel.getAllItems().get(level);
|
ValueRow row = stackProvider.panel.getAllItems().get(level);
|
||||||
|
|
||||||
DynamicTableColumn<ValueRow, String, Trace> levelCol =
|
DynamicTableColumn<ValueRow, String, Trace> levelCol =
|
||||||
stackProvider.panel.getColumnByNameAndType("Level", String.class);
|
stackProvider.panel.getColumnByNameAndType("Level", String.class).getValue();
|
||||||
DynamicTableColumn<ValueRow, ValueRow, Trace> pcCol =
|
DynamicTableColumn<ValueRow, ?, Trace> pcCol =
|
||||||
stackProvider.panel.getColumnByNameAndType("PC", ValueRow.class);
|
stackProvider.panel.getColumnByNameAndType("PC", ValueProperty.class).getValue();
|
||||||
DynamicTableColumn<ValueRow, Function, Trace> funcCol =
|
DynamicTableColumn<ValueRow, Function, Trace> funcCol =
|
||||||
stackProvider.panel.getColumnByNameAndType("Function", Function.class);
|
stackProvider.panel.getColumnByNameAndType("Function", Function.class).getValue();
|
||||||
|
|
||||||
assertEquals(PathUtils.makeKey(PathUtils.makeIndex(level)), rowColVal(row, levelCol));
|
assertEquals(PathUtils.makeKey(PathUtils.makeIndex(level)), rowColVal(row, levelCol));
|
||||||
assertEquals(pcVal, rowColVal(row, pcCol));
|
assertEquals(pcVal, rowColVal(row, pcCol));
|
||||||
|
|
|
@ -356,8 +356,8 @@ public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTra
|
||||||
DBTrace trace = object.getTrace();
|
DBTrace trace = object.getTrace();
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
|
case TargetMemoryRegion.RANGE_ATTRIBUTE_NAME:
|
||||||
trace.updateViewsChangeRegionBlockRange(this,
|
// NB. old/newValue are null here. The CREATED event just has the new entry.
|
||||||
(AddressRange) oldValue, (AddressRange) newValue);
|
trace.updateViewsRefreshBlocks();
|
||||||
return;
|
return;
|
||||||
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
|
case TargetObject.DISPLAY_ATTRIBUTE_NAME:
|
||||||
trace.updateViewsChangeRegionBlockName(this);
|
trace.updateViewsChangeRegionBlockName(this);
|
||||||
|
|
|
@ -44,7 +44,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
protected final DBTraceProgramView program;
|
protected final DBTraceProgramView program;
|
||||||
protected final DBTraceMemoryManager memoryManager;
|
protected final DBTraceMemoryManager memoryManager;
|
||||||
|
|
||||||
protected AddressSetView addressSet;
|
protected volatile AddressSetView addressSet;
|
||||||
protected boolean forceFullView = false;
|
protected boolean forceFullView = false;
|
||||||
protected long snap;
|
protected long snap;
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void computeFullAdddressSet() {
|
protected synchronized void computeFullAdddressSet() {
|
||||||
AddressSet temp = new AddressSet();
|
AddressSet temp = new AddressSet();
|
||||||
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
|
forPhysicalSpaces(space -> temp.add(space.getMinAddress(), space.getMaxAddress()));
|
||||||
addressSet = temp;
|
addressSet = temp;
|
||||||
|
@ -125,17 +125,17 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized AddressSetView getLoadedAndInitializedAddressSet() {
|
public AddressSetView getLoadedAndInitializedAddressSet() {
|
||||||
return addressSet;
|
return addressSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized AddressSetView getAllInitializedAddressSet() {
|
public AddressSetView getAllInitializedAddressSet() {
|
||||||
return addressSet;
|
return addressSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized AddressSetView getInitializedAddressSet() {
|
public AddressSetView getInitializedAddressSet() {
|
||||||
return addressSet;
|
return addressSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized long getSize() {
|
public long getSize() {
|
||||||
return addressSet.getNumAddresses();
|
return addressSet.getNumAddresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,11 +74,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void recomputeAddressSet() {
|
protected synchronized void recomputeAddressSet() {
|
||||||
AddressSet temp = new AddressSet();
|
AddressSet temp = new AddressSet();
|
||||||
// TODO: Performance test this
|
// TODO: Performance test this
|
||||||
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
||||||
addressSet = temp;
|
addressSet = temp;
|
||||||
|
System.err.println("Recomputed: " + temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MemoryBlock getRegionBlock(TraceMemoryRegion region) {
|
protected MemoryBlock getRegionBlock(TraceMemoryRegion region) {
|
||||||
|
@ -139,7 +140,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||||
|
|
||||||
public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange,
|
public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange,
|
||||||
AddressRange newRange) {
|
AddressRange newRange) {
|
||||||
// TODO: update cached block? Nothing to update.
|
|
||||||
changeRange(oldRange, newRange);
|
changeRange(oldRange, newRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -258,12 +258,22 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
rootSchema = schema;
|
rootSchema = schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void emitValueCreated(DBTraceObject parent, InternalTraceObjectValue entry) {
|
||||||
|
if (parent == null) {
|
||||||
|
// Don't need event for root value created
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parent.emitEvents(
|
||||||
|
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
|
||||||
|
}
|
||||||
|
|
||||||
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
|
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan,
|
||||||
DBTraceObject parent, String key, Object value) {
|
DBTraceObject parent, String key, Object value) {
|
||||||
if (value instanceof AddressRange) {
|
if (value instanceof AddressRange) {
|
||||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
||||||
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null);
|
.put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null);
|
||||||
entry.set(parent, key, false);
|
entry.set(parent, key, false);
|
||||||
|
emitValueCreated(parent, entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
else if (value instanceof Address) {
|
else if (value instanceof Address) {
|
||||||
|
@ -272,15 +282,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||||
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
DBTraceObjectAddressRangeValue entry = rangeValueMap
|
||||||
.put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null);
|
.put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null);
|
||||||
entry.set(parent, key, true);
|
entry.set(parent, key, true);
|
||||||
|
emitValueCreated(parent, entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
DBTraceObjectValue entry = valueStore.create();
|
DBTraceObjectValue entry = valueStore.create();
|
||||||
entry.set(lifespan, parent, key, value);
|
entry.set(lifespan, parent, key, value);
|
||||||
if (parent != null) {
|
emitValueCreated(parent, entry);
|
||||||
// Don't need event for root value created
|
|
||||||
parent.emitEvents(
|
|
||||||
new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CREATED, null, entry));
|
|
||||||
}
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue