mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-12-13 06:33:02 +00:00
GP-2772: Refactor Modules/Sections provider for new trace convention
This commit is contained in:
parent
5e89b1a886
commit
12f5365d40
@ -214,6 +214,9 @@ public class DebuggerLegacyRegionsPanel extends JPanel {
|
||||
add(regionFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
regionTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
myActionContext = new DebuggerRegionActionContext(provider,
|
||||
regionFilterPanel.getSelectedItems(), regionTable);
|
||||
contextChanged();
|
||||
|
@ -19,15 +19,9 @@ 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.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
@ -36,16 +30,12 @@ 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 {
|
||||
public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel<TraceObjectMemoryRegion> {
|
||||
|
||||
private static class RegionKeyColumn extends TraceValueKeyColumn {
|
||||
@Override
|
||||
@ -74,131 +64,47 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class ValueAddress extends ValueDerivedProperty<Address> {
|
||||
public ValueAddress(ValueRow row) {
|
||||
super(row, Address.class);
|
||||
private static class RegionStartColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
public RegionStartColumn() {
|
||||
super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@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) {
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMinAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RegionEndColumn extends RegionAddressColumn {
|
||||
private static class RegionEndColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
public RegionEndColumn() {
|
||||
super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "End";
|
||||
}
|
||||
|
||||
@Override
|
||||
Address fromRange(AddressRange range) {
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMaxAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RegionLengthColumn extends TraceValueObjectPropertyColumn<Long> {
|
||||
private static class RegionLengthColumn extends AbstractTraceValueObjectLengthColumn {
|
||||
public RegionLengthColumn() {
|
||||
super(Long.class);
|
||||
super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@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> {
|
||||
@ -245,7 +151,7 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
}
|
||||
}
|
||||
|
||||
private class RegionTableModel extends ObjectTableModel {
|
||||
private static class RegionTableModel extends ObjectTableModel {
|
||||
protected RegionTableModel(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
@ -256,7 +162,7 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
descriptor.addHiddenColumn(new RegionKeyColumn());
|
||||
descriptor.addHiddenColumn(new RegionPathColumn());
|
||||
descriptor.addVisibleColumn(new RegionNameColumn());
|
||||
descriptor.addVisibleColumn(new RegionStartColumn());
|
||||
descriptor.addVisibleColumn(new RegionStartColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new RegionEndColumn());
|
||||
descriptor.addVisibleColumn(new RegionLengthColumn());
|
||||
descriptor.addVisibleColumn(new RegionReadColumn());
|
||||
@ -266,19 +172,19 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerRegionsProvider provider;
|
||||
protected static ModelQuery successorRegions(TargetObjectSchema rootSchema, List<String> path) {
|
||||
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
|
||||
return new ModelQuery(schema.searchFor(TargetMemoryRegion.class, path, true));
|
||||
}
|
||||
|
||||
private DebuggerObjectActionContext myActionContext;
|
||||
protected static Set<TraceMemoryRegion> getSelectedRegions(DebuggerObjectActionContext ctx) {
|
||||
return ctx == null ? null
|
||||
: AbstractObjectsTableBasedPanel.getSelected(ctx, TraceObjectMemoryRegion.class)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public DebuggerRegionsPanel(DebuggerRegionsProvider provider) {
|
||||
super(provider.plugin);
|
||||
this.provider = provider;
|
||||
|
||||
setLimitToSnap(true);
|
||||
setShowHidden(false);
|
||||
|
||||
addSelectionListener(this);
|
||||
addCellActivationListener(this);
|
||||
super(provider.plugin, provider, TraceObjectMemoryRegion.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -286,19 +192,8 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
@ -312,64 +207,7 @@ public class DebuggerRegionsPanel extends ObjectsTablePanel
|
||||
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);
|
||||
}
|
||||
setSelected(sel);
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
|
||||
return legacyPanel.isContextNonEmpty(legacyCtx);
|
||||
}
|
||||
else if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
return panel.isContextNonEmpty(ctx);
|
||||
return DebuggerRegionsPanel.isContextNonEmpty(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,136 @@
|
||||
/* ###
|
||||
* 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 java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
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.services.DebuggerListingService;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectInterface;
|
||||
|
||||
public abstract class AbstractObjectsTableBasedPanel<U extends TraceObjectInterface>
|
||||
extends ObjectsTablePanel implements ListSelectionListener, CellActivationListener {
|
||||
|
||||
public static boolean isContextNonEmpty(DebuggerObjectActionContext ctx) {
|
||||
return ctx != null && !ctx.getObjectValues().isEmpty();
|
||||
}
|
||||
|
||||
public static <T extends TraceObjectInterface> Stream<T> getSelected(
|
||||
DebuggerObjectActionContext ctx, Class<T> iface) {
|
||||
return ctx == null ? null
|
||||
: ctx.getObjectValues()
|
||||
.stream()
|
||||
.filter(v -> v.isObject())
|
||||
.map(v -> v.getChild().queryInterface(iface))
|
||||
.filter(r -> r != null);
|
||||
}
|
||||
|
||||
private final ComponentProvider provider;
|
||||
private final Class<U> objType;
|
||||
|
||||
@AutoServiceConsumed
|
||||
protected DebuggerListingService listingService;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
protected DebuggerObjectActionContext myActionContext;
|
||||
|
||||
public AbstractObjectsTableBasedPanel(Plugin plugin, ComponentProvider provider,
|
||||
Class<U> objType) {
|
||||
super(plugin);
|
||||
this.provider = provider;
|
||||
this.objType = objType;
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
setLimitToSnap(true);
|
||||
setShowHidden(false);
|
||||
|
||||
addSelectionListener(this);
|
||||
addCellActivationListener(this);
|
||||
}
|
||||
|
||||
public DebuggerObjectActionContext getActionContext() {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
protected abstract ModelQuery computeQuery(TraceObject object);
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
TraceObject object = coordinates.getObject();
|
||||
setQuery(object == null ? ModelQuery.EMPTY : computeQuery(object));
|
||||
goToCoordinates(coordinates);
|
||||
}
|
||||
|
||||
protected void setSelected(Set<?> sel) {
|
||||
trySelect(sel.stream()
|
||||
.filter(s -> objType.isInstance(s))
|
||||
.map(s -> objType.cast(s).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 (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);
|
||||
}
|
||||
else if (propVal instanceof AddressRange range) {
|
||||
listingService.setCurrentSelection(
|
||||
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
|
||||
listingService.goTo(range.getMinAddress(), true);
|
||||
}
|
||||
}
|
||||
}
|
@ -189,13 +189,9 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean trySelect(Collection<TraceObject> objects) {
|
||||
public void 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() {
|
||||
|
@ -29,6 +29,7 @@ import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Lifespan.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
@ -56,13 +57,22 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||
|
||||
public T getValue();
|
||||
|
||||
public String getDisplay();
|
||||
default public String getDisplay() {
|
||||
T value = getValue();
|
||||
return value == null ? "" : value.toString();
|
||||
}
|
||||
|
||||
public String getHtmlDisplay();
|
||||
default public String getHtmlDisplay() {
|
||||
return "<html>" + HTMLUtilities.escapeHTML(getDisplay());
|
||||
}
|
||||
|
||||
public String getToolTip();
|
||||
default public String getToolTip() {
|
||||
return getDisplay();
|
||||
}
|
||||
|
||||
public boolean isModified();
|
||||
default public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ValueDerivedProperty<T> implements ValueProperty<T> {
|
||||
@ -85,6 +95,20 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ValueAddressProperty extends ValueDerivedProperty<Address> {
|
||||
public ValueAddressProperty(ValueRow row) {
|
||||
super(row, Address.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtmlDisplay() {
|
||||
Address value = getValue();
|
||||
return value == null ? ""
|
||||
: ("<html><body style='font-family:monospaced'>" +
|
||||
HTMLUtilities.escapeHTML(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public record ValueAttribute<T> (ValueRow row, String name, Class<T> type)
|
||||
implements ValueProperty<T> {
|
||||
public TraceObjectValue getEntry() {
|
||||
@ -132,6 +156,10 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||
public interface ValueRow {
|
||||
String getKey();
|
||||
|
||||
long currentSnap();
|
||||
|
||||
long previousSnap();
|
||||
|
||||
LifeSet getLife();
|
||||
|
||||
TraceObjectValue getValue();
|
||||
@ -192,6 +220,16 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||
return value.getEntryKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentSnap() {
|
||||
return getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long previousSnap() {
|
||||
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifeSet getLife() {
|
||||
MutableLifeSet life = new DefaultLifeSet();
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* ###
|
||||
* 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 ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public abstract class AbstractTraceValueObjectAddressColumn
|
||||
extends TraceValueObjectPropertyColumn<Address> {
|
||||
private final String attributeName;
|
||||
|
||||
public AbstractTraceValueObjectAddressColumn(String attributeName) {
|
||||
super(Address.class);
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
|
||||
protected abstract Address fromRange(AddressRange range);
|
||||
|
||||
@Override
|
||||
public ValueProperty<Address> getProperty(ValueRow row) {
|
||||
return new ValueAddressProperty(row) {
|
||||
@Override
|
||||
public Address getValue() {
|
||||
TraceObjectValue entry = row.getAttributeEntry(attributeName);
|
||||
if (entry == null || !(entry.getValue() instanceof AddressRange range)) {
|
||||
return null;
|
||||
}
|
||||
return fromRange(range);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* ###
|
||||
* 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 ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public abstract class AbstractTraceValueObjectLengthColumn
|
||||
extends TraceValueObjectPropertyColumn<Long> {
|
||||
|
||||
private final String attributeName;
|
||||
|
||||
public AbstractTraceValueObjectLengthColumn(String attributeName) {
|
||||
super(Long.class);
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
|
||||
protected Long fromRange(AddressRange range) {
|
||||
return range.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueProperty<Long> getProperty(ValueRow row) {
|
||||
return new ValueDerivedProperty<>(row, Long.class) {
|
||||
@Override
|
||||
public Long getValue() {
|
||||
TraceObjectValue entry = row.getAttributeEntry(attributeName);
|
||||
if (entry == null || !(entry.getValue() instanceof AddressRange range)) {
|
||||
return null;
|
||||
}
|
||||
return fromRange(range);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,308 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
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.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.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceModuleChangeType;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.util.database.ObjectKey;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerLegacyModulesPanel extends JPanel {
|
||||
|
||||
protected static Set<TraceModule> getSelectedModulesFromContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
return context.getSelectedModules()
|
||||
.stream()
|
||||
.map(r -> r.getModule())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSectionsFromContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
return context.getSelectedModules()
|
||||
.stream()
|
||||
.flatMap(r -> r.getModule().getSections().stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static AddressSetView getSelectedAddressesFromContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
AddressSet sel = new AddressSet();
|
||||
for (TraceModule module : getSelectedModulesFromContext(context)) {
|
||||
sel.add(module.getRange());
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
|
||||
protected static ModuleRow getSelectedModuleRowFromContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
Set<ModuleRow> modules = context.getSelectedModules();
|
||||
if (modules.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return modules.iterator().next();
|
||||
}
|
||||
|
||||
protected static SectionRow getSelectedSectionRowFromContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
Set<SectionRow> sections = context.getSelectedSections();
|
||||
if (sections.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return sections.iterator().next();
|
||||
}
|
||||
|
||||
protected enum ModuleTableColumns
|
||||
implements EnumeratedTableColumn<ModuleTableColumns, ModuleRow> {
|
||||
BASE("Base Address", Address.class, ModuleRow::getBase),
|
||||
MAX("Max Address", Address.class, ModuleRow::getMaxAddress),
|
||||
SHORT_NAME("Name", String.class, ModuleRow::getShortName),
|
||||
NAME("Module Name", String.class, ModuleRow::getName, ModuleRow::setName),
|
||||
LIFESPAN("Lifespan", Lifespan.class, ModuleRow::getLifespan),
|
||||
LENGTH("Length", Long.class, ModuleRow::getLength);
|
||||
|
||||
private final String header;
|
||||
private final Function<ModuleRow, ?> getter;
|
||||
private final BiConsumer<ModuleRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> ModuleTableColumns(String header, Class<T> cls, Function<ModuleRow, T> getter,
|
||||
BiConsumer<ModuleRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<ModuleRow, Object>) setter;
|
||||
}
|
||||
|
||||
<T> ModuleTableColumns(String header, Class<T> cls, Function<ModuleRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(ModuleRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(ModuleRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(ModuleRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ModuleTableModel
|
||||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
|
||||
|
||||
public ModuleTableModel(PluginTool tool) {
|
||||
super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
|
||||
ModuleRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModuleTableColumns> defaultSortOrder() {
|
||||
return List.of(ModuleTableColumns.BASE);
|
||||
}
|
||||
}
|
||||
|
||||
private class ModulesListener extends TraceDomainObjectListener {
|
||||
public ModulesListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||
|
||||
listenFor(TraceModuleChangeType.ADDED, this::moduleAdded);
|
||||
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.DELETED, this::moduleDeleted);
|
||||
}
|
||||
|
||||
private void objectRestored() {
|
||||
loadModules();
|
||||
}
|
||||
|
||||
private void moduleAdded(TraceModule module) {
|
||||
moduleTableModel.addItem(module);
|
||||
}
|
||||
|
||||
private void moduleChanged(TraceModule module) {
|
||||
moduleTableModel.updateItem(module);
|
||||
}
|
||||
|
||||
private void moduleDeleted(TraceModule module) {
|
||||
moduleTableModel.deleteItem(module);
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerModulesProvider provider;
|
||||
|
||||
Trace currentTrace;
|
||||
|
||||
private final ModulesListener modulesListener = new ModulesListener();
|
||||
protected final ModuleTableModel moduleTableModel;
|
||||
protected final GhidraTable moduleTable;
|
||||
final GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
|
||||
|
||||
private DebuggerModuleActionContext myActionContext;
|
||||
|
||||
public DebuggerLegacyModulesPanel(DebuggerModulesProvider provider) {
|
||||
super(new BorderLayout());
|
||||
this.provider = provider;
|
||||
|
||||
moduleTableModel = new ModuleTableModel(provider.getTool());
|
||||
moduleTable = new GhidraTable(moduleTableModel);
|
||||
moduleTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
add(new JScrollPane(moduleTable));
|
||||
moduleFilterPanel = new GhidraTableFilterPanel<>(moduleTable, moduleTableModel);
|
||||
add(moduleFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
moduleTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
myActionContext = new DebuggerModuleActionContext(provider,
|
||||
moduleFilterPanel.getSelectedItems(), moduleTable);
|
||||
provider.legacyModulesPanelContextChanged();
|
||||
});
|
||||
moduleTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
navigateToSelectedModule();
|
||||
}
|
||||
}
|
||||
});
|
||||
moduleTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
navigateToSelectedModule();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Adjust default column widths?
|
||||
TableColumnModel colModel = moduleTable.getColumnModel();
|
||||
|
||||
TableColumn baseCol = colModel.getColumn(ModuleTableColumns.BASE.ordinal());
|
||||
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn maxCol = colModel.getColumn(ModuleTableColumns.MAX.ordinal());
|
||||
maxCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn lenCol = colModel.getColumn(ModuleTableColumns.LENGTH.ordinal());
|
||||
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||
}
|
||||
|
||||
protected void contextChanged() {
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
protected void navigateToSelectedModule() {
|
||||
if (provider.listingService != null) {
|
||||
int selectedRow = moduleTable.getSelectedRow();
|
||||
int selectedColumn = moduleTable.getSelectedColumn();
|
||||
Object value = moduleTable.getValueAt(selectedRow, selectedColumn);
|
||||
if (value instanceof Address) {
|
||||
provider.listingService.goTo((Address) value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DebuggerModuleActionContext getActionContext() {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
private void loadModules() {
|
||||
moduleTable.getSelectionModel().clearSelection();
|
||||
moduleTableModel.clear();
|
||||
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TraceModuleManager moduleManager = currentTrace.getModuleManager();
|
||||
moduleTableModel.addAllItems(moduleManager.getAllModules());
|
||||
}
|
||||
|
||||
public void setTrace(Trace trace) {
|
||||
if (currentTrace == trace) {
|
||||
return;
|
||||
}
|
||||
removeOldListeners();
|
||||
currentTrace = trace;
|
||||
addNewListeners();
|
||||
loadModules();
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
setTrace(coordinates.getTrace());
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.removeListener(modulesListener);
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.addListener(modulesListener);
|
||||
}
|
||||
|
||||
public void setSelectedModules(Set<TraceModule> sel) {
|
||||
DebuggerResources.setSelectedRows(sel, moduleTableModel::getRow, moduleTable,
|
||||
moduleTableModel, moduleFilterPanel);
|
||||
}
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
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.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import docking.widgets.table.TableFilter;
|
||||
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.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceModuleChangeType;
|
||||
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.util.database.ObjectKey;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerLegacySectionsPanel extends JPanel {
|
||||
|
||||
protected static Set<TraceModule> getSelectedModulesFromContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
return context.getSelectedSections()
|
||||
.stream()
|
||||
.map(r -> r.getModule())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSectionsFromContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
return context.getSelectedSections()
|
||||
.stream()
|
||||
.map(r -> r.getSection())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static AddressSetView getSelectedAddressesFromContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
AddressSet sel = new AddressSet();
|
||||
for (TraceSection section : getSelectedSectionsFromContext(context)) {
|
||||
sel.add(section.getRange());
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
|
||||
protected enum SectionTableColumns
|
||||
implements EnumeratedTableColumn<SectionTableColumns, SectionRow> {
|
||||
START("Start Address", Address.class, SectionRow::getStart),
|
||||
END("End Address", Address.class, SectionRow::getEnd),
|
||||
NAME("Section Name", String.class, SectionRow::getName, SectionRow::setName),
|
||||
MODULE("Module Name", String.class, SectionRow::getModuleName),
|
||||
LENGTH("Length", Long.class, SectionRow::getLength);
|
||||
|
||||
private final String header;
|
||||
private final Function<SectionRow, ?> getter;
|
||||
private final BiConsumer<SectionRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> SectionTableColumns(String header, Class<T> cls, Function<SectionRow, T> getter,
|
||||
BiConsumer<SectionRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<SectionRow, Object>) setter;
|
||||
}
|
||||
|
||||
<T> SectionTableColumns(String header, Class<T> cls, Function<SectionRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(SectionRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(SectionRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(SectionRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SectionTableModel
|
||||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
|
||||
|
||||
public SectionTableModel(PluginTool tool) {
|
||||
super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
|
||||
SectionRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SectionTableColumns> defaultSortOrder() {
|
||||
return List.of(SectionTableColumns.START);
|
||||
}
|
||||
}
|
||||
|
||||
private class SectionsListener extends TraceDomainObjectListener {
|
||||
public SectionsListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||
|
||||
/**
|
||||
* NOTE: No need for Module.ADDED here. A TraceModule is created empty, so when each
|
||||
* section is added, we'll get the call.
|
||||
*/
|
||||
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.DELETED, this::moduleDeleted);
|
||||
|
||||
listenFor(TraceSectionChangeType.ADDED, this::sectionAdded);
|
||||
listenFor(TraceSectionChangeType.CHANGED, this::sectionChanged);
|
||||
listenFor(TraceSectionChangeType.DELETED, this::sectionDeleted);
|
||||
}
|
||||
|
||||
private void objectRestored() {
|
||||
loadSections();
|
||||
}
|
||||
|
||||
private void moduleChanged(TraceModule module) {
|
||||
sectionTableModel.fireTableDataChanged(); // Because module name in section row
|
||||
}
|
||||
|
||||
private void moduleDeleted(TraceModule module) {
|
||||
// NOTE: module.getSections() will be empty, now
|
||||
sectionTableModel.deleteAllItems(sectionTableModel.getMap()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(r -> r.getModule() == module)
|
||||
.map(r -> r.getSection())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void sectionAdded(TraceSection section) {
|
||||
sectionTableModel.addItem(section);
|
||||
}
|
||||
|
||||
private void sectionChanged(TraceSection section) {
|
||||
sectionTableModel.updateItem(section);
|
||||
}
|
||||
|
||||
private void sectionDeleted(TraceSection section) {
|
||||
sectionTableModel.deleteItem(section);
|
||||
}
|
||||
}
|
||||
|
||||
class SectionsBySelectedModulesTableFilter implements TableFilter<SectionRow> {
|
||||
@Override
|
||||
public boolean acceptsRow(SectionRow sectionRow) {
|
||||
List<ModuleRow> selModuleRows =
|
||||
provider.legacyModulesPanel.moduleFilterPanel.getSelectedItems();
|
||||
if (selModuleRows == null || selModuleRows.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (ModuleRow moduleRow : selModuleRows) {
|
||||
if (moduleRow.getModule() == sectionRow.getModule()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerModulesProvider provider;
|
||||
|
||||
private Trace currentTrace;
|
||||
|
||||
private final SectionsListener sectionsListener = new SectionsListener();
|
||||
protected final SectionTableModel sectionTableModel;
|
||||
protected final GhidraTable sectionTable;
|
||||
protected final GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
|
||||
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
|
||||
new SectionsBySelectedModulesTableFilter();
|
||||
|
||||
private DebuggerSectionActionContext myActionContext;
|
||||
|
||||
public DebuggerLegacySectionsPanel(DebuggerModulesProvider provider) {
|
||||
super(new BorderLayout());
|
||||
this.provider = provider;
|
||||
|
||||
sectionTableModel = new SectionTableModel(provider.getTool());
|
||||
sectionTable = new GhidraTable(sectionTableModel);
|
||||
sectionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
add(new JScrollPane(sectionTable));
|
||||
sectionFilterPanel = new GhidraTableFilterPanel<>(sectionTable, sectionTableModel);
|
||||
add(sectionFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
sectionTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
myActionContext = new DebuggerSectionActionContext(provider,
|
||||
sectionFilterPanel.getSelectedItems(), sectionTable);
|
||||
provider.legacySectionsPanelContextChanged();
|
||||
});
|
||||
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
|
||||
sectionTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
navigateToSelectedSection();
|
||||
}
|
||||
}
|
||||
});
|
||||
sectionTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
navigateToSelectedSection();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TableColumnModel colModel = sectionTable.getColumnModel();
|
||||
TableColumn startCol = colModel.getColumn(SectionTableColumns.START.ordinal());
|
||||
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn endCol = colModel.getColumn(SectionTableColumns.END.ordinal());
|
||||
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn lenCol = colModel.getColumn(SectionTableColumns.LENGTH.ordinal());
|
||||
lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||
}
|
||||
|
||||
protected void contextChanged() {
|
||||
provider.contextChanged();
|
||||
}
|
||||
|
||||
protected void navigateToSelectedSection() {
|
||||
if (provider.listingService != null) {
|
||||
int selectedRow = sectionTable.getSelectedRow();
|
||||
int selectedColumn = sectionTable.getSelectedColumn();
|
||||
Object value = sectionTable.getValueAt(selectedRow, selectedColumn);
|
||||
if (value instanceof Address) {
|
||||
provider.listingService.goTo((Address) value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DebuggerSectionActionContext getActionContext() {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
void loadSections() {
|
||||
sectionTable.getSelectionModel().clearSelection();
|
||||
sectionTableModel.clear();
|
||||
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TraceModuleManager moduleManager = currentTrace.getModuleManager();
|
||||
sectionTableModel.addAllItems(moduleManager.getAllSections());
|
||||
}
|
||||
|
||||
public void setTrace(Trace trace) {
|
||||
if (currentTrace == trace) {
|
||||
return;
|
||||
}
|
||||
removeOldListeners();
|
||||
currentTrace = trace;
|
||||
addNewListeners();
|
||||
loadSections();
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
setTrace(coordinates.getTrace());
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.removeListener(sectionsListener);
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.addListener(sectionsListener);
|
||||
}
|
||||
|
||||
public void setSelectedSections(Set<TraceSection> sel) {
|
||||
DebuggerResources.setSelectedRows(sel, sectionTableModel::getRow, sectionTable,
|
||||
sectionTableModel, sectionFilterPanel);
|
||||
}
|
||||
|
||||
public void setFilteredBySelectedModules(boolean filtered) {
|
||||
sectionFilterPanel.setSecondaryFilter(filtered ? filterSectionsBySelectedModules : null);
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
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.*;
|
||||
import ghidra.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceObjectModule> {
|
||||
|
||||
private static class ModuleBaseColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
public ModuleBaseColumn() {
|
||||
super(TargetModule.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Base";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMinAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ModuleMaxColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
public ModuleMaxColumn() {
|
||||
super(TargetModule.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Max";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMaxAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ModuleNameColumn extends TraceValueObjectAttributeColumn<String> {
|
||||
public ModuleNameColumn() {
|
||||
super(TargetModule.MODULE_NAME_ATTRIBUTE_NAME, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Name";
|
||||
}
|
||||
}
|
||||
|
||||
private static class ModulePathColumn 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 ModuleLengthColumn extends AbstractTraceValueObjectLengthColumn {
|
||||
public ModuleLengthColumn() {
|
||||
super(TargetModule.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Length";
|
||||
}
|
||||
}
|
||||
|
||||
private static class ModuleTableModel extends ObjectTableModel {
|
||||
protected ModuleTableModel(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||
descriptor.addHiddenColumn(new ModulePathColumn());
|
||||
descriptor.addVisibleColumn(new ModuleBaseColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new ModuleMaxColumn());
|
||||
descriptor.addVisibleColumn(new ModuleNameColumn());
|
||||
descriptor.addVisibleColumn(new ModuleLengthColumn());
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
protected static Set<TraceModule> getSelectedModulesFromContext(
|
||||
DebuggerObjectActionContext ctx) {
|
||||
Set<TraceModule> result = new HashSet<>();
|
||||
for (TraceObjectValue value : ctx.getObjectValues()) {
|
||||
TraceObject child = value.getChild();
|
||||
TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
|
||||
if (module != null) {
|
||||
result.add(module);
|
||||
continue;
|
||||
}
|
||||
TraceObjectSection section = child.queryInterface(TraceObjectSection.class);
|
||||
if (section != null) {
|
||||
result.add(section.getModule());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSectionsFromContext(
|
||||
DebuggerObjectActionContext ctx) {
|
||||
Set<TraceSection> result = new HashSet<>();
|
||||
for (TraceObjectValue value : ctx.getObjectValues()) {
|
||||
TraceObject child = value.getChild();
|
||||
TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
|
||||
if (module != null) {
|
||||
result.addAll(module.getSections());
|
||||
continue;
|
||||
}
|
||||
TraceObjectSection section = child.queryInterface(TraceObjectSection.class);
|
||||
if (section != null) {
|
||||
result.add(section);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AddressSetView getSelectedAddressesFromContext(DebuggerObjectActionContext ctx) {
|
||||
AddressSet result = new AddressSet();
|
||||
for (TraceObjectValue value : ctx.getObjectValues()) {
|
||||
TraceObject child = value.getChild();
|
||||
TraceObjectModule module = child.queryInterface(TraceObjectModule.class);
|
||||
if (module != null) {
|
||||
result.add(module.getRange());
|
||||
continue;
|
||||
}
|
||||
TraceObjectSection section = child.queryInterface(TraceObjectSection.class);
|
||||
if (section != null) {
|
||||
result.add(section.getRange());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static ModelQuery successorModules(TargetObjectSchema rootSchema, List<String> path) {
|
||||
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
|
||||
return new ModelQuery(schema.searchFor(TargetModule.class, path, true));
|
||||
}
|
||||
|
||||
private final DebuggerModulesProvider provider;
|
||||
|
||||
public DebuggerModulesPanel(DebuggerModulesProvider provider) {
|
||||
super(provider.plugin, provider, TraceObjectModule.class);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectTableModel createModel(Plugin plugin) {
|
||||
return new ModuleTableModel(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelQuery computeQuery(TraceObject object) {
|
||||
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
|
||||
List<String> seedPath = object.getCanonicalPath().getKeyList();
|
||||
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
|
||||
if (processPath != null) {
|
||||
return successorModules(rootSchema, processPath);
|
||||
}
|
||||
List<String> containerPath =
|
||||
rootSchema.searchForSuitableContainer(TargetModule.class, seedPath);
|
||||
|
||||
if (containerPath != null) {
|
||||
return successorModules(rootSchema, containerPath);
|
||||
}
|
||||
return successorModules(rootSchema, List.of());
|
||||
}
|
||||
|
||||
public void setSelectedModules(Set<TraceModule> sel) {
|
||||
setSelected(sel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
super.valueChanged(e);
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
provider.modulesPanelContextChanged();
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,39 +15,35 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import docking.widgets.table.TableFilter;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.plugin.core.debug.service.modules.MapModulesBackgroundCommand;
|
||||
import ghidra.app.plugin.core.debug.service.modules.MapSectionsBackgroundCommand;
|
||||
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
|
||||
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.*;
|
||||
@ -60,18 +56,26 @@ import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceModuleChangeType;
|
||||
import ghidra.trace.model.Trace.TraceSectionChangeType;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.ObjectKey;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerModulesProvider 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 MapIdenticallyAction {
|
||||
String NAME = DebuggerResources.NAME_MAP_IDENTICALLY;
|
||||
String DESCRIPTION = DebuggerResources.DESCRIPTION_MAP_IDENTICALLY;
|
||||
@ -179,270 +183,43 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
protected enum ModuleTableColumns
|
||||
implements EnumeratedTableColumn<ModuleTableColumns, ModuleRow> {
|
||||
BASE("Base Address", Address.class, ModuleRow::getBase),
|
||||
MAX("Max Address", Address.class, ModuleRow::getMaxAddress),
|
||||
SHORT_NAME("Name", String.class, ModuleRow::getShortName),
|
||||
NAME("Module Name", String.class, ModuleRow::getName, ModuleRow::setName),
|
||||
LIFESPAN("Lifespan", Lifespan.class, ModuleRow::getLifespan),
|
||||
LENGTH("Length", Long.class, ModuleRow::getLength);
|
||||
|
||||
private final String header;
|
||||
private final Function<ModuleRow, ?> getter;
|
||||
private final BiConsumer<ModuleRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> ModuleTableColumns(String header, Class<T> cls, Function<ModuleRow, T> getter,
|
||||
BiConsumer<ModuleRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<ModuleRow, Object>) setter;
|
||||
}
|
||||
|
||||
<T> ModuleTableColumns(String header, Class<T> cls, Function<ModuleRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(ModuleRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(ModuleRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(ModuleRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum SectionTableColumns
|
||||
implements EnumeratedTableColumn<SectionTableColumns, SectionRow> {
|
||||
START("Start Address", Address.class, SectionRow::getStart),
|
||||
END("End Address", Address.class, SectionRow::getEnd),
|
||||
NAME("Section Name", String.class, SectionRow::getName, SectionRow::setName),
|
||||
MODULE("Module Name", String.class, SectionRow::getModuleName),
|
||||
LENGTH("Length", Long.class, SectionRow::getLength);
|
||||
|
||||
private final String header;
|
||||
private final Function<SectionRow, ?> getter;
|
||||
private final BiConsumer<SectionRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> SectionTableColumns(String header, Class<T> cls, Function<SectionRow, T> getter,
|
||||
BiConsumer<SectionRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<SectionRow, Object>) setter;
|
||||
}
|
||||
|
||||
<T> SectionTableColumns(String header, Class<T> cls, Function<SectionRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(SectionRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(SectionRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(SectionRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected static ModuleRow getSelectedModuleRow(ActionContext context) {
|
||||
if (!(context instanceof DebuggerModuleActionContext)) {
|
||||
return null;
|
||||
}
|
||||
DebuggerModuleActionContext ctx = (DebuggerModuleActionContext) context;
|
||||
Set<ModuleRow> modules = ctx.getSelectedModules();
|
||||
if (modules.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return modules.iterator().next();
|
||||
}
|
||||
|
||||
protected static SectionRow getSelectedSectionRow(ActionContext context) {
|
||||
if (!(context instanceof DebuggerSectionActionContext)) {
|
||||
return null;
|
||||
}
|
||||
DebuggerSectionActionContext ctx = (DebuggerSectionActionContext) context;
|
||||
Set<SectionRow> sections = ctx.getSelectedSections();
|
||||
if (sections.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return sections.iterator().next();
|
||||
}
|
||||
|
||||
protected static class ModuleTableModel
|
||||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
|
||||
|
||||
public ModuleTableModel(PluginTool tool) {
|
||||
super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
|
||||
ModuleRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModuleTableColumns> defaultSortOrder() {
|
||||
return List.of(ModuleTableColumns.BASE);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SectionTableModel
|
||||
extends DebouncedRowWrappedEnumeratedColumnTableModel< //
|
||||
SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
|
||||
|
||||
public SectionTableModel(PluginTool tool) {
|
||||
super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
|
||||
SectionRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SectionTableColumns> defaultSortOrder() {
|
||||
return List.of(SectionTableColumns.START);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Set<TraceModule> getSelectedModulesFromModuleContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
return context.getSelectedModules()
|
||||
.stream()
|
||||
.map(r -> r.getModule())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceModule> getSelectedModulesFromSectionContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
return context.getSelectedSections()
|
||||
.stream()
|
||||
.map(r -> r.getModule())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSectionsFromModuleContext(
|
||||
DebuggerModuleActionContext context) {
|
||||
return context.getSelectedModules()
|
||||
.stream()
|
||||
.flatMap(r -> r.getModule().getSections().stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSectionsFromSectionContext(
|
||||
DebuggerSectionActionContext context) {
|
||||
return context.getSelectedSections()
|
||||
.stream()
|
||||
.map(r -> r.getSection())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected static Set<TraceModule> getSelectedModules(ActionContext context) {
|
||||
if (context instanceof DebuggerModuleActionContext) {
|
||||
return getSelectedModulesFromModuleContext((DebuggerModuleActionContext) context);
|
||||
if (context instanceof DebuggerModuleActionContext ctx) {
|
||||
return DebuggerLegacyModulesPanel.getSelectedModulesFromContext(ctx);
|
||||
}
|
||||
if (context instanceof DebuggerSectionActionContext) {
|
||||
return getSelectedModulesFromSectionContext((DebuggerSectionActionContext) context);
|
||||
if (context instanceof DebuggerSectionActionContext ctx) {
|
||||
return DebuggerLegacySectionsPanel.getSelectedModulesFromContext(ctx);
|
||||
}
|
||||
if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
return DebuggerModulesPanel.getSelectedModulesFromContext(ctx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static Set<TraceSection> getSelectedSections(ActionContext context) {
|
||||
if (context instanceof DebuggerModuleActionContext) {
|
||||
return getSelectedSectionsFromModuleContext((DebuggerModuleActionContext) context);
|
||||
if (context instanceof DebuggerModuleActionContext ctx) {
|
||||
return DebuggerLegacyModulesPanel.getSelectedSectionsFromContext(ctx);
|
||||
}
|
||||
if (context instanceof DebuggerSectionActionContext) {
|
||||
return getSelectedSectionsFromSectionContext((DebuggerSectionActionContext) context);
|
||||
if (context instanceof DebuggerSectionActionContext ctx) {
|
||||
return DebuggerLegacySectionsPanel.getSelectedSectionsFromContext(ctx);
|
||||
}
|
||||
if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
return DebuggerModulesPanel.getSelectedSectionsFromContext(ctx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class ModulesListener extends TraceDomainObjectListener {
|
||||
public ModulesListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||
|
||||
listenFor(TraceModuleChangeType.ADDED, this::moduleAdded);
|
||||
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged);
|
||||
listenFor(TraceModuleChangeType.DELETED, this::moduleDeleted);
|
||||
|
||||
listenFor(TraceSectionChangeType.ADDED, this::sectionAdded);
|
||||
listenFor(TraceSectionChangeType.CHANGED, this::sectionChanged);
|
||||
listenFor(TraceSectionChangeType.DELETED, this::sectionDeleted);
|
||||
protected static AddressSetView getSelectedAddresses(ActionContext context) {
|
||||
if (context instanceof DebuggerModuleActionContext ctx) {
|
||||
return DebuggerLegacyModulesPanel.getSelectedAddressesFromContext(ctx);
|
||||
}
|
||||
|
||||
private void objectRestored() {
|
||||
loadModules();
|
||||
if (context instanceof DebuggerSectionActionContext ctx) {
|
||||
return DebuggerLegacySectionsPanel.getSelectedAddressesFromContext(ctx);
|
||||
}
|
||||
|
||||
private void moduleAdded(TraceModule module) {
|
||||
moduleTableModel.addItem(module);
|
||||
/**
|
||||
* NOTE: No need to add sections here. A TraceModule is created empty, so when each
|
||||
* section is added, we'll get the call.
|
||||
*/
|
||||
}
|
||||
|
||||
private void moduleChanged(TraceModule module) {
|
||||
moduleTableModel.updateItem(module);
|
||||
sectionTableModel.fireTableDataChanged(); // Because module name in section row
|
||||
}
|
||||
|
||||
private void moduleDeleted(TraceModule module) {
|
||||
moduleTableModel.deleteItem(module);
|
||||
// NOTE: module.getSections() will be empty, now
|
||||
sectionTableModel.deleteAllItems(sectionTableModel.getMap()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(r -> r.getModule() == module)
|
||||
.map(r -> r.getSection())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void sectionAdded(TraceSection section) {
|
||||
sectionTableModel.addItem(section);
|
||||
}
|
||||
|
||||
private void sectionChanged(TraceSection section) {
|
||||
sectionTableModel.updateItem(section);
|
||||
}
|
||||
|
||||
private void sectionDeleted(TraceSection section) {
|
||||
sectionTableModel.deleteItem(section);
|
||||
if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
return DebuggerModulesPanel.getSelectedAddressesFromContext(ctx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected class RecordersChangedListener implements CollectionChangeListener<TraceRecorder> {
|
||||
@ -480,20 +257,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
AddressSet sel = new AddressSet();
|
||||
if (myActionContext instanceof DebuggerModuleActionContext) {
|
||||
DebuggerModuleActionContext mCtx =
|
||||
(DebuggerModuleActionContext) myActionContext;
|
||||
for (TraceModule module : getSelectedModulesFromModuleContext(mCtx)) {
|
||||
sel.add(module.getRange());
|
||||
}
|
||||
}
|
||||
else if (myActionContext instanceof DebuggerSectionActionContext) {
|
||||
DebuggerSectionActionContext sCtx =
|
||||
(DebuggerSectionActionContext) myActionContext;
|
||||
for (TraceSection section : getSelectedSectionsFromSectionContext(sCtx)) {
|
||||
sel.add(section.getRange());
|
||||
}
|
||||
AddressSetView sel = getSelectedAddresses(context);
|
||||
if (sel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
sel = sel.intersect(traceManager.getCurrentView().getMemory());
|
||||
@ -521,12 +287,12 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
Set<TraceModule> modules = getSelectedModules(myActionContext);
|
||||
Set<TraceModule> modules = getSelectedModules(context);
|
||||
if (modules == null) {
|
||||
return;
|
||||
}
|
||||
TraceRecorder recorder = modelService.getRecorder(currentTrace);
|
||||
BackgroundUtils.async(tool, currentTrace, "Capture Types", true, true, false,
|
||||
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
||||
BackgroundUtils.async(tool, current.getTrace(), "Capture Types", true, true, false,
|
||||
(__, monitor) -> AsyncUtils.each(TypeSpec.VOID, modules.iterator(), (m, loop) -> {
|
||||
if (recorder.getTargetModule(m) == null) {
|
||||
loop.repeatWhile(!monitor.isCancelled());
|
||||
@ -541,7 +307,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return isCaptureApplicable(myActionContext);
|
||||
return isCaptureApplicable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,12 +324,12 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
Set<TraceModule> modules = getSelectedModules(myActionContext);
|
||||
Set<TraceModule> modules = getSelectedModules(context);
|
||||
if (modules == null) {
|
||||
return;
|
||||
}
|
||||
TraceRecorder recorder = modelService.getRecorder(currentTrace);
|
||||
BackgroundUtils.async(tool, currentTrace, NAME, true, true, false,
|
||||
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
||||
BackgroundUtils.async(tool, current.getTrace(), NAME, true, true, false,
|
||||
(__, monitor) -> AsyncUtils.each(TypeSpec.VOID, modules.iterator(), (m, loop) -> {
|
||||
if (recorder.getTargetModule(m) == null) {
|
||||
loop.repeatWhile(!monitor.isCancelled());
|
||||
@ -578,7 +344,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return isCaptureApplicable(myActionContext);
|
||||
return isCaptureApplicable(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -597,7 +363,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
if (importerService == null) {
|
||||
return;
|
||||
}
|
||||
Set<TraceModule> modules = getSelectedModules(myActionContext);
|
||||
Set<TraceModule> modules = getSelectedModules(context);
|
||||
if (modules == null || modules.size() != 1) {
|
||||
return;
|
||||
}
|
||||
@ -607,33 +373,17 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
Set<TraceModule> sel = getSelectedModules(myActionContext);
|
||||
return importerService != null && sel != null && sel.size() == 1;
|
||||
try {
|
||||
Set<TraceModule> sel = getSelectedModules(context);
|
||||
return importerService != null && sel != null && sel.size() == 1;
|
||||
}
|
||||
catch (TraceClosedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SectionsBySelectedModulesTableFilter implements TableFilter<SectionRow> {
|
||||
@Override
|
||||
public boolean acceptsRow(SectionRow sectionRow) {
|
||||
List<ModuleRow> selModuleRows = moduleFilterPanel.getSelectedItems();
|
||||
if (selModuleRows == null || selModuleRows.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (ModuleRow moduleRow : selModuleRows) {
|
||||
if (moduleRow.getModule() == sectionRow.getModule()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerModulesPlugin plugin;
|
||||
final DebuggerModulesPlugin plugin;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
private DebuggerModelService modelService;
|
||||
@ -642,7 +392,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@AutoServiceConsumed
|
||||
private DebuggerListingService listingService;
|
||||
DebuggerListingService listingService;
|
||||
@AutoServiceConsumed
|
||||
private DebuggerConsoleService consoleService;
|
||||
@AutoServiceConsumed
|
||||
@ -654,35 +404,28 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
Trace currentTrace;
|
||||
|
||||
private final ModulesListener modulesListener = new ModulesListener();
|
||||
|
||||
private final RecordersChangedListener recordersChangedListener =
|
||||
new RecordersChangedListener();
|
||||
|
||||
protected final ModuleTableModel moduleTableModel;
|
||||
protected GhidraTable moduleTable;
|
||||
private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
|
||||
|
||||
protected final SectionTableModel sectionTableModel;
|
||||
protected GhidraTable sectionTable;
|
||||
protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
|
||||
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
|
||||
new SectionsBySelectedModulesTableFilter();
|
||||
|
||||
private final JSplitPane mainPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
|
||||
DebuggerModulesPanel modulesPanel;
|
||||
DebuggerLegacyModulesPanel legacyModulesPanel;
|
||||
DebuggerSectionsPanel sectionsPanel;
|
||||
DebuggerLegacySectionsPanel legacySectionsPanel;
|
||||
|
||||
// TODO: Lazy construction of these dialogs?
|
||||
private final DebuggerBlockChooserDialog blockChooserDialog;
|
||||
private final DebuggerModuleMapProposalDialog moduleProposalDialog;
|
||||
private final DebuggerSectionMapProposalDialog sectionProposalDialog;
|
||||
private DataTreeDialog programChooserDialog; // Already lazy
|
||||
|
||||
private ActionContext myActionContext;
|
||||
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
private Program currentProgram;
|
||||
private ProgramLocation currentLocation;
|
||||
|
||||
private ActionContext myActionContext;
|
||||
|
||||
DockingAction actionMapIdentically;
|
||||
DockingAction actionMapManually;
|
||||
DockingAction actionMapModules;
|
||||
@ -706,9 +449,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null);
|
||||
this.plugin = plugin;
|
||||
|
||||
moduleTableModel = new ModuleTableModel(tool);
|
||||
sectionTableModel = new SectionTableModel(tool);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_MODULES);
|
||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
@ -773,6 +513,10 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean isLegacy(Trace trace) {
|
||||
return trace != null && trace.getObjectManager().getRootSchema() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext == null) {
|
||||
@ -781,125 +525,54 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
private void loadModules() {
|
||||
moduleTable.getSelectionModel().clearSelection();
|
||||
moduleTableModel.clear();
|
||||
sectionTable.getSelectionModel().clearSelection();
|
||||
sectionTableModel.clear();
|
||||
protected boolean isFilterSectionsByModules() {
|
||||
// TODO: Make this a proper field and save it to tool state
|
||||
return actionFilterSectionsByModules.isSelected();
|
||||
}
|
||||
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
void modulesPanelContextChanged() {
|
||||
myActionContext = modulesPanel.getActionContext();
|
||||
if (isFilterSectionsByModules()) {
|
||||
sectionsPanel.reload();
|
||||
}
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
TraceModuleManager moduleManager = currentTrace.getModuleManager();
|
||||
moduleTableModel.addAllItems(moduleManager.getAllModules());
|
||||
sectionTableModel.addAllItems(moduleManager.getAllSections());
|
||||
void legacyModulesPanelContextChanged() {
|
||||
myActionContext = legacyModulesPanel.getActionContext();
|
||||
if (isFilterSectionsByModules()) {
|
||||
legacySectionsPanel.loadSections();
|
||||
}
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
void sectionsPanelContextChanged() {
|
||||
myActionContext = sectionsPanel.getActionContext();
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
void legacySectionsPanelContextChanged() {
|
||||
myActionContext = legacySectionsPanel.getActionContext();
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
protected void buildMainPanel() {
|
||||
mainPanel.setContinuousLayout(true);
|
||||
|
||||
JPanel modulePanel = new JPanel(new BorderLayout());
|
||||
moduleTable = new GhidraTable(moduleTableModel);
|
||||
moduleTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
modulePanel.add(new JScrollPane(moduleTable));
|
||||
moduleFilterPanel = new GhidraTableFilterPanel<>(moduleTable, moduleTableModel);
|
||||
modulePanel.add(moduleFilterPanel, BorderLayout.SOUTH);
|
||||
mainPanel.setLeftComponent(modulePanel);
|
||||
modulesPanel = new DebuggerModulesPanel(this);
|
||||
mainPanel.setLeftComponent(modulesPanel);
|
||||
legacyModulesPanel = new DebuggerLegacyModulesPanel(this);
|
||||
|
||||
JPanel sectionPanel = new JPanel(new BorderLayout());
|
||||
sectionTable = new GhidraTable(sectionTableModel);
|
||||
sectionTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
sectionPanel.add(new JScrollPane(sectionTable));
|
||||
sectionFilterPanel = new GhidraTableFilterPanel<>(sectionTable, sectionTableModel);
|
||||
sectionPanel.add(sectionFilterPanel, BorderLayout.SOUTH);
|
||||
mainPanel.setRightComponent(sectionPanel);
|
||||
sectionsPanel = new DebuggerSectionsPanel(this);
|
||||
mainPanel.setRightComponent(sectionsPanel);
|
||||
legacySectionsPanel = new DebuggerLegacySectionsPanel(this);
|
||||
|
||||
mainPanel.setResizeWeight(0.5);
|
||||
|
||||
moduleTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
myActionContext = new DebuggerModuleActionContext(this,
|
||||
moduleFilterPanel.getSelectedItems(), moduleTable);
|
||||
contextChanged();
|
||||
if (actionFilterSectionsByModules.isSelected()) {
|
||||
sectionTableModel.fireTableDataChanged();
|
||||
}
|
||||
});
|
||||
moduleTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
navigateToSelectedModule();
|
||||
}
|
||||
}
|
||||
});
|
||||
sectionTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
myActionContext = new DebuggerSectionActionContext(this,
|
||||
sectionFilterPanel.getSelectedItems(), sectionTable);
|
||||
contextChanged();
|
||||
});
|
||||
// Note, ProgramTableModel will not work here, since that would navigate the "static" view
|
||||
sectionTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
navigateToSelectedSection();
|
||||
}
|
||||
}
|
||||
});
|
||||
sectionTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
navigateToSelectedSection();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Adjust default column widths?
|
||||
TableColumnModel modColModel = moduleTable.getColumnModel();
|
||||
|
||||
TableColumn baseCol = modColModel.getColumn(ModuleTableColumns.BASE.ordinal());
|
||||
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn maxCol = modColModel.getColumn(ModuleTableColumns.MAX.ordinal());
|
||||
maxCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn mLenCol = modColModel.getColumn(ModuleTableColumns.LENGTH.ordinal());
|
||||
mLenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||
|
||||
TableColumnModel secColModel = sectionTable.getColumnModel();
|
||||
TableColumn startCol = secColModel.getColumn(SectionTableColumns.START.ordinal());
|
||||
startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn endCol = secColModel.getColumn(SectionTableColumns.END.ordinal());
|
||||
endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn sLenCol = secColModel.getColumn(SectionTableColumns.LENGTH.ordinal());
|
||||
sLenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX);
|
||||
}
|
||||
|
||||
protected void navigateToSelectedModule() {
|
||||
if (listingService != null) {
|
||||
int selectedRow = moduleTable.getSelectedRow();
|
||||
int selectedColumn = moduleTable.getSelectedColumn();
|
||||
Object value = moduleTable.getValueAt(selectedRow, selectedColumn);
|
||||
if (value instanceof Address) {
|
||||
listingService.goTo((Address) value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void navigateToSelectedSection() {
|
||||
if (listingService != null) {
|
||||
int selectedRow = sectionTable.getSelectedRow();
|
||||
int selectedColumn = sectionTable.getSelectedColumn();
|
||||
Object value = sectionTable.getValueAt(selectedRow, selectedColumn);
|
||||
if (value instanceof Address) {
|
||||
listingService.goTo((Address) value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionMapIdentically = MapIdenticallyAction.builder(plugin)
|
||||
.enabledWhen(ctx -> currentProgram != null && currentTrace != null)
|
||||
.enabledWhen(ctx -> currentProgram != null && current.getTrace() != null)
|
||||
.onAction(this::activatedMapIdentically)
|
||||
.buildAndInstallLocal(this);
|
||||
actionMapManually = MapManuallyAction.builder(plugin)
|
||||
@ -953,7 +626,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
.onAction(this::toggledFilter)
|
||||
.buildAndInstallLocal(this);
|
||||
actionSelectCurrent = SelectRowsAction.builder(plugin)
|
||||
.enabledWhen(ctx -> currentTrace != null)
|
||||
.enabledWhen(ctx -> current.getTrace() != null)
|
||||
.description("Select modules and sections by trace selection")
|
||||
.onAction(this::activatedSelectCurrent)
|
||||
.buildAndInstallLocal(this);
|
||||
@ -961,15 +634,16 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private boolean isContextNonEmpty(ActionContext ignored) {
|
||||
if (myActionContext instanceof DebuggerModuleActionContext) {
|
||||
DebuggerModuleActionContext ctx = (DebuggerModuleActionContext) myActionContext;
|
||||
private boolean isContextNonEmpty(ActionContext context) {
|
||||
if (context instanceof DebuggerModuleActionContext ctx) {
|
||||
return !ctx.getSelectedModules().isEmpty();
|
||||
}
|
||||
if (myActionContext instanceof DebuggerSectionActionContext) {
|
||||
DebuggerSectionActionContext ctx = (DebuggerSectionActionContext) myActionContext;
|
||||
if (context instanceof DebuggerSectionActionContext ctx) {
|
||||
return !ctx.getSelectedSections().isEmpty();
|
||||
}
|
||||
if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
return !ctx.getObjectValues().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -982,10 +656,10 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
|
||||
private void activatedMapIdentically(ActionContext ignored) {
|
||||
if (currentProgram == null || currentTrace == null) {
|
||||
if (currentProgram == null || current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
staticMappingService.addIdentityMapping(currentTrace, currentProgram,
|
||||
staticMappingService.addIdentityMapping(current.getTrace(), currentProgram,
|
||||
Lifespan.nowOn(traceManager.getCurrentSnap()), true);
|
||||
}
|
||||
|
||||
@ -1063,21 +737,18 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
|
||||
private void toggledFilter(ActionContext ignored) {
|
||||
if (actionFilterSectionsByModules.isSelected()) {
|
||||
sectionFilterPanel.setSecondaryFilter(filterSectionsBySelectedModules);
|
||||
}
|
||||
else {
|
||||
sectionFilterPanel.setSecondaryFilter(null);
|
||||
}
|
||||
boolean filtered = isFilterSectionsByModules();
|
||||
sectionsPanel.setFilteredBySelectedModules(filtered);
|
||||
legacySectionsPanel.setFilteredBySelectedModules(filtered);
|
||||
}
|
||||
|
||||
private void activatedSelectCurrent(ActionContext ignored) {
|
||||
if (listingService == null || traceManager == null || currentTrace == null) {
|
||||
if (listingService == null || traceManager == null || current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramSelection progSel = listingService.getCurrentSelection();
|
||||
TraceModuleManager moduleManager = currentTrace.getModuleManager();
|
||||
TraceModuleManager moduleManager = current.getTrace().getModuleManager();
|
||||
if (progSel != null && !progSel.isEmpty()) {
|
||||
long snap = traceManager.getCurrentSnap();
|
||||
Set<TraceModule> modSel = new HashSet<>();
|
||||
@ -1141,21 +812,29 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
if (modelService == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(context instanceof DebuggerModuleActionContext)) {
|
||||
if (current.getTrace() == null) {
|
||||
return false;
|
||||
}
|
||||
DebuggerModuleActionContext ctx = (DebuggerModuleActionContext) context;
|
||||
if (ctx.getSelectedModules().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (currentTrace == null) {
|
||||
return false;
|
||||
}
|
||||
TraceRecorder recorder = modelService.getRecorder(currentTrace);
|
||||
TraceRecorder recorder = modelService.getRecorder(current.getTrace());
|
||||
if (recorder == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (context instanceof DebuggerModuleActionContext ctx) {
|
||||
if (!ctx.getSelectedModules().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (context instanceof DebuggerObjectActionContext ctx) {
|
||||
if (!ctx.getObjectValues().isEmpty()) {
|
||||
return ctx.getObjectValues()
|
||||
.get(0)
|
||||
.getChild()
|
||||
.getTargetSchema()
|
||||
.getInterfaces()
|
||||
.contains(TargetModule.class);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void promptModuleProposal(Collection<ModuleMapEntry> proposal) {
|
||||
@ -1171,7 +850,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
tool.executeBackgroundCommand(
|
||||
new MapModulesBackgroundCommand(staticMappingService, adjusted), currentTrace);
|
||||
new MapModulesBackgroundCommand(staticMappingService, adjusted), current.getTrace());
|
||||
}
|
||||
|
||||
protected void mapModules(Set<TraceModule> modules) {
|
||||
@ -1210,7 +889,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
tool.executeBackgroundCommand(
|
||||
new MapSectionsBackgroundCommand(staticMappingService, adjusted), currentTrace);
|
||||
new MapSectionsBackgroundCommand(staticMappingService, adjusted), current.getTrace());
|
||||
}
|
||||
|
||||
protected void mapSections(Set<TraceSection> sections) {
|
||||
@ -1341,39 +1020,60 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrace(Trace trace) {
|
||||
if (currentTrace == trace) {
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
if (sameCoordinates(current, coordinates)) {
|
||||
current = coordinates;
|
||||
return;
|
||||
}
|
||||
removeOldListeners();
|
||||
currentTrace = trace;
|
||||
addNewListeners();
|
||||
loadModules();
|
||||
|
||||
current = coordinates;
|
||||
|
||||
if (isLegacy(coordinates.getTrace())) {
|
||||
modulesPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
sectionsPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
legacyModulesPanel.coordinatesActivated(coordinates);
|
||||
legacySectionsPanel.coordinatesActivated(coordinates);
|
||||
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyModulesPanel) == -1) {
|
||||
mainPanel.remove(modulesPanel);
|
||||
mainPanel.remove(sectionsPanel);
|
||||
mainPanel.setLeftComponent(legacyModulesPanel);
|
||||
mainPanel.setRightComponent(legacySectionsPanel);
|
||||
mainPanel.validate();
|
||||
}
|
||||
}
|
||||
else {
|
||||
legacyModulesPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
legacySectionsPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
modulesPanel.coordinatesActivated(coordinates);
|
||||
sectionsPanel.coordinatesActivated(coordinates);
|
||||
if (ArrayUtils.indexOf(mainPanel.getComponents(), modulesPanel) == -1) {
|
||||
mainPanel.remove(legacyModulesPanel);
|
||||
mainPanel.remove(legacySectionsPanel);
|
||||
mainPanel.setLeftComponent(modulesPanel);
|
||||
mainPanel.setRightComponent(sectionsPanel);
|
||||
mainPanel.validate();
|
||||
}
|
||||
}
|
||||
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.removeListener(modulesListener);
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
currentTrace.addListener(modulesListener);
|
||||
}
|
||||
|
||||
public void setSelectedModules(Set<TraceModule> sel) {
|
||||
DebuggerResources.setSelectedRows(sel, moduleTableModel::getRow, moduleTable,
|
||||
moduleTableModel, moduleFilterPanel);
|
||||
if (isLegacy(current.getTrace())) {
|
||||
legacyModulesPanel.setSelectedModules(sel);
|
||||
}
|
||||
else {
|
||||
modulesPanel.setSelectedModules(sel);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedSections(Set<TraceSection> sel) {
|
||||
DebuggerResources.setSelectedRows(sel, sectionTableModel::getRow, sectionTable,
|
||||
sectionTableModel, sectionFilterPanel);
|
||||
if (isLegacy(current.getTrace())) {
|
||||
legacySectionsPanel.setSelectedSections(sel);
|
||||
}
|
||||
else {
|
||||
sectionsPanel.setSelectedSections(sel);
|
||||
}
|
||||
}
|
||||
|
||||
private DataTreeDialog getProgramChooserDialog() {
|
||||
|
@ -0,0 +1,268 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import docking.widgets.table.TableFilter;
|
||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||
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.dbg.util.PathUtils;
|
||||
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.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.modules.TraceSection;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class DebuggerSectionsPanel extends AbstractObjectsTableBasedPanel<TraceObjectSection> {
|
||||
|
||||
private static class SectionStartColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
|
||||
public SectionStartColumn() {
|
||||
super(TargetSection.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Start";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMinAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SectionEndColumn extends AbstractTraceValueObjectAddressColumn {
|
||||
public SectionEndColumn() {
|
||||
super(TargetSection.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "End";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address fromRange(AddressRange range) {
|
||||
return range.getMaxAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SectionNameColumn extends TraceValueKeyColumn {
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
String key = rowObject.getValue().getEntryKey();
|
||||
if (PathUtils.isIndex(key)) {
|
||||
return PathUtils.parseIndex(key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SectionPathColumn 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 SectionModuleNameColumn extends TraceValueObjectPropertyColumn<String> {
|
||||
public SectionModuleNameColumn() {
|
||||
super(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Module Name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueProperty<String> getProperty(ValueRow row) {
|
||||
return new ValueDerivedProperty<>(row, String.class) {
|
||||
@Override
|
||||
public String getValue() {
|
||||
TraceObject module = getModule(row);
|
||||
if (module == null) {
|
||||
return "";
|
||||
}
|
||||
TraceObjectValue nameEntry = module.getAttribute(row.currentSnap(),
|
||||
TargetModule.MODULE_NAME_ATTRIBUTE_NAME);
|
||||
if (nameEntry == null) {
|
||||
return "";
|
||||
}
|
||||
return nameEntry.getValue().toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static class SectionLengthColumn extends AbstractTraceValueObjectLengthColumn {
|
||||
public SectionLengthColumn() {
|
||||
super(TargetSection.RANGE_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Length";
|
||||
}
|
||||
}
|
||||
|
||||
private class SectionTableModel extends ObjectTableModel {
|
||||
protected SectionTableModel(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||
descriptor.addHiddenColumn(new SectionPathColumn());
|
||||
descriptor.addVisibleColumn(new SectionStartColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new SectionEndColumn());
|
||||
descriptor.addVisibleColumn(new SectionNameColumn());
|
||||
descriptor.addVisibleColumn(new SectionModuleNameColumn());
|
||||
descriptor.addVisibleColumn(new SectionLengthColumn());
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
private static TraceObject getModule(ValueRow row) {
|
||||
TraceObjectValue moduleEntry =
|
||||
row.getAttributeEntry(TargetSection.MODULE_ATTRIBUTE_NAME);
|
||||
if (moduleEntry != null && moduleEntry.isObject()) {
|
||||
return moduleEntry.getChild();
|
||||
}
|
||||
return row.getValue()
|
||||
.getChild()
|
||||
.queryCanonicalAncestorsTargetInterface(TargetModule.class)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected static ModelQuery successorSections(TargetObjectSchema rootSchema,
|
||||
List<String> path) {
|
||||
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
|
||||
return new ModelQuery(schema.searchFor(TargetSection.class, path, true));
|
||||
}
|
||||
|
||||
private class SectionsBySelectedModulesTableFilter implements TableFilter<ValueRow> {
|
||||
@Override
|
||||
public boolean acceptsRow(ValueRow rowObject) {
|
||||
if (selectedModuleObjects.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
TraceObject module = getModule(rowObject);
|
||||
return selectedModuleObjects.contains(module);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerModulesProvider provider;
|
||||
private Set<Object> selectedModuleObjects = Set.of();
|
||||
|
||||
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
|
||||
new SectionsBySelectedModulesTableFilter();
|
||||
|
||||
public DebuggerSectionsPanel(DebuggerModulesProvider provider) {
|
||||
super(provider.plugin, provider, TraceObjectSection.class);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectTableModel createModel(Plugin plugin) {
|
||||
return new SectionTableModel(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelQuery computeQuery(TraceObject object) {
|
||||
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
|
||||
List<String> seedPath = object.getCanonicalPath().getKeyList();
|
||||
List<String> processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath);
|
||||
if (processPath != null) {
|
||||
return successorSections(rootSchema, processPath);
|
||||
}
|
||||
// Yes, anchor on the *module* container when searching for sections
|
||||
List<String> containerPath =
|
||||
rootSchema.searchForSuitableContainer(TargetModule.class, seedPath);
|
||||
|
||||
if (containerPath != null) {
|
||||
return successorSections(rootSchema, containerPath);
|
||||
}
|
||||
return successorSections(rootSchema, List.of());
|
||||
}
|
||||
|
||||
public void setFilteredBySelectedModules(boolean filtered) {
|
||||
if (filtered) {
|
||||
refreshSelectedModuleObjects();
|
||||
}
|
||||
filterPanel.setSecondaryFilter(filtered ? filterSectionsBySelectedModules : null);
|
||||
}
|
||||
|
||||
public void setSelectedSections(Set<TraceSection> sel) {
|
||||
setSelected(sel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
super.valueChanged(e);
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
provider.sectionsPanelContextChanged();
|
||||
}
|
||||
|
||||
private void refreshSelectedModuleObjects() {
|
||||
selectedModuleObjects = provider.modulesPanel.getSelectedItems()
|
||||
.stream()
|
||||
.map(r -> r.getValue().getValue())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
refreshSelectedModuleObjects();
|
||||
super.reload();
|
||||
}
|
||||
}
|
@ -16,9 +16,7 @@
|
||||
package ghidra.app.plugin.core.debug.gui.stack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
@ -27,28 +25,28 @@ import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.dbg.target.TargetStack;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathMatcher;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.stack.TraceObjectStackFrame;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
|
||||
implements ListSelectionListener, CellActivationListener {
|
||||
|
||||
private static class FrameLevelColumn extends TraceValueKeyColumn {
|
||||
@ -99,7 +97,7 @@ public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
@Override
|
||||
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||
descriptor.addVisibleColumn(new FrameLevelColumn());
|
||||
descriptor.addVisibleColumn(new FrameLevelColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new FramePcColumn());
|
||||
descriptor.addVisibleColumn(new FrameFunctionColumn());
|
||||
return descriptor;
|
||||
@ -108,28 +106,14 @@ public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
|
||||
private final DebuggerStackProvider provider;
|
||||
|
||||
@AutoServiceConsumed
|
||||
protected DebuggerListingService listingService;
|
||||
@AutoServiceConsumed
|
||||
protected DebuggerTraceManagerService traceManager;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
||||
|
||||
private DebuggerObjectActionContext myActionContext;
|
||||
|
||||
public DebuggerStackPanel(Plugin plugin, DebuggerStackProvider provider) {
|
||||
super(plugin);
|
||||
public DebuggerStackPanel(DebuggerStackProvider provider) {
|
||||
super(provider.plugin, provider, TraceObjectStackFrame.class);
|
||||
this.provider = provider;
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
setLimitToSnap(true);
|
||||
setShowHidden(false);
|
||||
|
||||
addSelectionListener(this);
|
||||
addCellActivationListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,14 +121,8 @@ public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
return new StackTableModel(plugin);
|
||||
}
|
||||
|
||||
public DebuggerObjectActionContext getActionContext() {
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelQuery computeQuery(TraceObject object) {
|
||||
if (object == null) {
|
||||
return ModelQuery.EMPTY;
|
||||
}
|
||||
TargetObjectSchema rootSchema = object.getRoot().getTargetSchema();
|
||||
List<String> stackPath = rootSchema
|
||||
.searchForSuitable(TargetStack.class, object.getCanonicalPath().getKeyList());
|
||||
@ -156,11 +134,10 @@ public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
return new ModelQuery(matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
super.coordinatesActivated(coordinates);
|
||||
TraceObject object = coordinates.getObject();
|
||||
setQuery(computeQuery(object));
|
||||
goToCoordinates(coordinates);
|
||||
|
||||
if (object != null) {
|
||||
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
||||
trySelectAncestor(object);
|
||||
@ -170,34 +147,13 @@ public class DebuggerStackPanel extends ObjectsTablePanel
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
super.valueChanged(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);
|
||||
}
|
||||
ValueRow item = getSelectedItem();
|
||||
if (item != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
return true;
|
||||
}
|
||||
|
||||
private final DebuggerStackPlugin plugin;
|
||||
final DebuggerStackPlugin plugin;
|
||||
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
|
||||
@ -90,7 +90,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
|
||||
protected void buildMainPanel() {
|
||||
panel = new DebuggerStackPanel(plugin, this);
|
||||
panel = new DebuggerStackPanel(this);
|
||||
mainPanel.add(panel);
|
||||
legacyPanel = new DebuggerLegacyStackPanel(plugin, this);
|
||||
}
|
||||
|
@ -21,7 +21,11 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
@ -174,6 +178,11 @@ class ObjectRecorder {
|
||||
if (attribute instanceof TargetBreakpointKindSet) {
|
||||
return encodeEnumSet((TargetBreakpointKindSet) attribute);
|
||||
}
|
||||
if (attribute instanceof TargetDataType dataType) {
|
||||
// NOTE: some are also TargetObject, but that gets checked first
|
||||
JsonElement element = dataType.toJson();
|
||||
return new Gson().toJson(element);
|
||||
}
|
||||
if (attribute instanceof TargetExecutionState) {
|
||||
return encodeEnum((TargetExecutionState) attribute);
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.Trace.TraceThreadChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
@ -100,6 +101,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
this.trace = trace;
|
||||
listenFor(TraceThreadChangeType.ADDED, this::threadAdded);
|
||||
listenFor(TraceThreadChangeType.DELETED, this::threadDeleted);
|
||||
listenFor(TraceObjectChangeType.CREATED, this::objectCreated);
|
||||
}
|
||||
|
||||
private void threadAdded(TraceThread thread) {
|
||||
@ -129,6 +131,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
activate(current.thread(null));
|
||||
}
|
||||
}
|
||||
|
||||
private void objectCreated(TraceObject object) {
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (supportsFocus(recorder)) {
|
||||
return;
|
||||
}
|
||||
if (current.getTrace() != trace) {
|
||||
return;
|
||||
}
|
||||
if (!object.isRoot()) {
|
||||
return;
|
||||
}
|
||||
activate(current.object(object));
|
||||
}
|
||||
}
|
||||
|
||||
static class TransactionEndFuture extends CompletableFuture<Void>
|
||||
|
@ -149,7 +149,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
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 =
|
||||
DynamicTableColumn<ValueRow, ?, Trace> nameCol =
|
||||
provider.panel.getColumnByNameAndType("Name", ValueRow.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> startCol =
|
||||
provider.panel.getColumnByNameAndType("Start", ValueProperty.class).getValue();
|
||||
@ -181,9 +181,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
}
|
||||
|
||||
@After
|
||||
public void testDownRegionsProviderTest() throws Exception {
|
||||
public void tearDownRegionsProviderTest() throws Exception {
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
runSwing(() -> traceManager.closeAllTraces());
|
||||
}
|
||||
|
@ -16,9 +16,15 @@
|
||||
package ghidra.app.plugin.core.debug.gui.model;
|
||||
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class QueryPanelTestHelper {
|
||||
public static GhidraTable getTable(AbstractQueryTablePanel<?, ?> panel) {
|
||||
return panel.table;
|
||||
}
|
||||
|
||||
public static <T> GhidraTableFilterPanel<T> getFilterPanel(
|
||||
AbstractQueryTablePanel<T, ?> panel) {
|
||||
return panel.filterPanel;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,772 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import generic.Unique;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.core.debug.gui.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractImportFromFileSystemAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
|
||||
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.modules.DebuggerModuleMapProposalDialog.ModuleMapTableColumns;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
|
||||
import ghidra.dbg.model.TestTargetModule;
|
||||
import ghidra.dbg.model.TestTargetTypedefDataType;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.plugin.importer.ImporterPlugin;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.data.TraceBasedDataTypeManager;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.memory.TraceOverlappedRegionException;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.trace.model.symbol.TraceSymbol;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
|
||||
public class DebuggerModulesProviderLegacyTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
protected DebuggerModulesPlugin modulesPlugin;
|
||||
protected DebuggerModulesProvider modulesProvider;
|
||||
|
||||
protected TraceModule modExe;
|
||||
protected TraceSection secExeText;
|
||||
protected TraceSection secExeData;
|
||||
|
||||
protected TraceModule modLib;
|
||||
protected TraceSection secLibText;
|
||||
protected TraceSection secLibData;
|
||||
|
||||
@Before
|
||||
public void setUpModulesProviderTest() throws Exception {
|
||||
modulesPlugin = addPlugin(tool, DebuggerModulesPlugin.class);
|
||||
modulesProvider = waitForComponentProvider(DebuggerModulesProvider.class);
|
||||
}
|
||||
|
||||
protected void addRegionsFromModules()
|
||||
throws TraceOverlappedRegionException, DuplicateNameException {
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
DBTraceMemoryManager manager = tb.trace.getMemoryManager();
|
||||
for (TraceModule module : tb.trace.getModuleManager().getAllModules()) {
|
||||
for (TraceSection section : module.getSections()) {
|
||||
Set<TraceMemoryFlag> flags = new HashSet<>();
|
||||
flags.add(TraceMemoryFlag.READ);
|
||||
if (".text".equals(section.getName())) {
|
||||
flags.add(TraceMemoryFlag.EXECUTE);
|
||||
}
|
||||
else if (".data".equals(section.getName())) {
|
||||
flags.add(TraceMemoryFlag.WRITE);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
manager.addRegion(
|
||||
"Processes[1].Memory[" + module.getName() + ":" + section.getName() + "]",
|
||||
module.getLifespan(), section.getRange(), flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void addModules() throws Exception {
|
||||
TraceModuleManager manager = tb.trace.getModuleManager();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe = manager.addLoadedModule("Processes[1].Modules[first_proc]", "first_proc",
|
||||
tb.range(0x55550000, 0x5575007f), 0);
|
||||
secExeText = modExe.addSection("Processes[1].Modules[first_proc].Sections[.text]",
|
||||
".text", tb.range(0x55550000, 0x555500ff));
|
||||
secExeData = modExe.addSection("Processes[1].Modules[first_proc].Sections[.data]",
|
||||
".data", tb.range(0x55750000, 0x5575007f));
|
||||
|
||||
modLib = manager.addLoadedModule("Processes[1].Modules[some_lib]", "some_lib",
|
||||
tb.range(0x7f000000, 0x7f10003f), 0);
|
||||
secLibText = modLib.addSection("Processes[1].Modules[some_lib].Sections[.text]",
|
||||
".text", tb.range(0x7f000000, 0x7f0003ff));
|
||||
secLibData = modLib.addSection("Processes[1].Modules[some_lib].Sections[.data]",
|
||||
".data", tb.range(0x7f100000, 0x7f10003f));
|
||||
}
|
||||
}
|
||||
|
||||
protected MemoryBlock addBlock() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
|
||||
return program.getMemory()
|
||||
.createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, monitor,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertProviderEmpty() {
|
||||
List<ModuleRow> modulesDisplayed =
|
||||
modulesProvider.legacyModulesPanel.moduleTableModel.getModelData();
|
||||
assertTrue(modulesDisplayed.isEmpty());
|
||||
|
||||
List<SectionRow> sectionsDisplayed =
|
||||
modulesProvider.legacySectionsPanel.sectionTableModel.getModelData();
|
||||
assertTrue(sectionsDisplayed.isEmpty());
|
||||
}
|
||||
|
||||
protected void assertProviderPopulated() {
|
||||
List<ModuleRow> modulesDisplayed =
|
||||
new ArrayList<>(modulesProvider.legacyModulesPanel.moduleTableModel.getModelData());
|
||||
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
|
||||
// I should be able to assume this is sorted by base address. It's the default sort column.
|
||||
assertEquals(2, modulesDisplayed.size());
|
||||
|
||||
ModuleRow execRow = modulesDisplayed.get(0);
|
||||
assertEquals(tb.addr(0x55550000), execRow.getBase());
|
||||
assertEquals("first_proc", execRow.getName());
|
||||
|
||||
// Use only (start) offset for excess, as unique ID
|
||||
ModuleRow libRow = modulesDisplayed.get(1);
|
||||
assertEquals(tb.addr(0x7f000000), libRow.getBase());
|
||||
|
||||
List<SectionRow> sectionsDisplayed =
|
||||
new ArrayList<>(modulesProvider.legacySectionsPanel.sectionTableModel.getModelData());
|
||||
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
|
||||
assertEquals(4, sectionsDisplayed.size());
|
||||
|
||||
SectionRow execTextRow = sectionsDisplayed.get(0);
|
||||
assertEquals(tb.addr(0x55550000), execTextRow.getStart());
|
||||
assertEquals(tb.addr(0x555500ff), execTextRow.getEnd());
|
||||
assertEquals("first_proc", execTextRow.getModuleName());
|
||||
assertEquals(".text", execTextRow.getName());
|
||||
assertEquals(256, execTextRow.getLength());
|
||||
|
||||
SectionRow execDataRow = sectionsDisplayed.get(1);
|
||||
assertEquals(tb.addr(0x55750000), execDataRow.getStart());
|
||||
|
||||
SectionRow libTextRow = sectionsDisplayed.get(2);
|
||||
assertEquals(tb.addr(0x7f000000), libTextRow.getStart());
|
||||
|
||||
SectionRow libDataRow = sectionsDisplayed.get(3);
|
||||
assertEquals(tb.addr(0x7f100000), libDataRow.getStart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() throws Exception {
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThenAddModulesPopulatesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
addModules();
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddModulesThenActivatePopulatesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
waitForSwing();
|
||||
|
||||
assertProviderEmpty();
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockChooserDialogPopulates() throws Exception {
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
intoProject(tb.trace);
|
||||
intoProject(program);
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
MemoryBlock block = addBlock();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setName(modExe.getName());
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(
|
||||
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
|
||||
|
||||
runSwing(() -> modulesProvider.setSelectedSections(Set.of(secExeText)));
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionMapSections, false);
|
||||
|
||||
DebuggerSectionMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||
|
||||
DebuggerBlockChooserDialog blockDialog =
|
||||
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||
|
||||
assertEquals(1, blockDialog.getTableModel().getRowCount());
|
||||
MemoryBlockRow row = blockDialog.getTableModel().getModelData().get(0);
|
||||
assertEquals(program, row.getProgram());
|
||||
assertEquals(block, row.getBlock());
|
||||
// NOTE: Other getters should be tested in a separate MemoryBlockRowTest
|
||||
|
||||
pressButtonByText(blockDialog, "Cancel", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveModulesRemovedFromProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe.delete();
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
List<ModuleRow> modulesDisplayed =
|
||||
new ArrayList<>(modulesProvider.legacyModulesPanel.moduleTableModel.getModelData());
|
||||
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
|
||||
assertEquals(1, modulesDisplayed.size());
|
||||
|
||||
ModuleRow libRow = modulesDisplayed.get(0);
|
||||
assertEquals("some_lib", libRow.getName());
|
||||
|
||||
List<SectionRow> sectionsDisplayed =
|
||||
new ArrayList<>(modulesProvider.legacySectionsPanel.sectionTableModel.getModelData());
|
||||
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
|
||||
assertEquals(2, sectionsDisplayed.size());
|
||||
|
||||
SectionRow libTextRow = sectionsDisplayed.get(0);
|
||||
assertEquals(".text", libTextRow.getName());
|
||||
assertEquals("some_lib", libTextRow.getModuleName());
|
||||
|
||||
SectionRow libDataRow = sectionsDisplayed.get(1);
|
||||
assertEquals(".data", libDataRow.getName());
|
||||
assertEquals("some_lib", libDataRow.getModuleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
|
||||
undo(tb.trace);
|
||||
assertProviderEmpty();
|
||||
|
||||
redo(tb.trace);
|
||||
assertProviderPopulated();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivatingNoTraceEmptiesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertProviderPopulated();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionMapIdentically() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapIdentically.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
intoProject(tb.trace);
|
||||
intoProject(program);
|
||||
|
||||
// No modules necessary
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(modulesProvider.actionMapIdentically.isEnabled());
|
||||
|
||||
// Need some substance in the program
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate")) {
|
||||
addBlock();
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionMapIdentically, true);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
Collection<? extends TraceStaticMapping> mappings =
|
||||
tb.trace.getStaticMappingManager().getAllEntries();
|
||||
assertEquals(1, mappings.size());
|
||||
|
||||
TraceStaticMapping sm = mappings.iterator().next();
|
||||
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||
assertEquals("ram:00400000", sm.getStaticAddress());
|
||||
assertEquals(0x1000, sm.getLength()); // Block is 0x1000 in length
|
||||
assertEquals(tb.addr(0x00400000), sm.getMinTraceAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionMapModules() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapModules.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
intoProject(tb.trace);
|
||||
intoProject(program);
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionMapModules.isEnabled());
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setImageBase(addr(program, 0x00400000), true);
|
||||
program.setName(modExe.getName());
|
||||
|
||||
addBlock(); // So the program has a size
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(
|
||||
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionMapModules.isEnabled());
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionMapModules, false);
|
||||
|
||||
DebuggerModuleMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
|
||||
|
||||
List<ModuleMapEntry> proposal = propDialog.getTableModel().getModelData();
|
||||
ModuleMapEntry entry = Unique.assertOne(proposal);
|
||||
assertEquals(modExe, entry.getModule());
|
||||
assertEquals(program, entry.getToProgram());
|
||||
|
||||
clickTableCell(propDialog.getTable(), 0, ModuleMapTableColumns.CHOOSE.ordinal(), 1);
|
||||
|
||||
DataTreeDialog programDialog = waitForDialogComponent(DataTreeDialog.class);
|
||||
assertEquals(program.getDomainFile(), programDialog.getDomainFile());
|
||||
|
||||
pressButtonByText(programDialog, "OK", true);
|
||||
|
||||
assertEquals(program, entry.getToProgram());
|
||||
// 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(1, mappings.size());
|
||||
|
||||
TraceStaticMapping sm = mappings.iterator().next();
|
||||
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||
assertEquals("ram:00400000", sm.getStaticAddress());
|
||||
assertEquals(0x1000, sm.getLength()); // Block is 0x1000 in length
|
||||
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||
}
|
||||
|
||||
// TODO: testActionMapModulesTo
|
||||
// TODO: testActionMapModuleTo
|
||||
|
||||
@Test
|
||||
public void testActionMapSections() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
intoProject(tb.trace);
|
||||
intoProject(program);
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
||||
|
||||
MemoryBlock block = addBlock();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setName(modExe.getName());
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(
|
||||
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
|
||||
|
||||
modulesProvider.setSelectedSections(Set.of(secExeText));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionMapSections.isEnabled());
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionMapSections, false);
|
||||
|
||||
DebuggerSectionMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||
|
||||
List<SectionMapEntry> proposal = propDialog.getTableModel().getModelData();
|
||||
SectionMapEntry entry = Unique.assertOne(proposal);
|
||||
assertEquals(secExeText, entry.getSection());
|
||||
assertEquals(block, entry.getBlock());
|
||||
|
||||
clickTableCell(propDialog.getTable(), 0, SectionMapTableColumns.CHOOSE.ordinal(), 1);
|
||||
|
||||
DebuggerBlockChooserDialog blockDialog =
|
||||
waitForDialogComponent(DebuggerBlockChooserDialog.class);
|
||||
MemoryBlockRow row = Unique.assertOne(blockDialog.getTableModel().getModelData());
|
||||
assertEquals(block, row.getBlock());
|
||||
|
||||
pressButtonByText(blockDialog, "OK", true);
|
||||
assertEquals(block, 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(1, mappings.size());
|
||||
|
||||
TraceStaticMapping sm = mappings.iterator().next();
|
||||
assertEquals(Lifespan.nowOn(0), sm.getLifespan());
|
||||
assertEquals("ram:00400000", sm.getStaticAddress());
|
||||
assertEquals(0x100, sm.getLength()); // Section is 0x100, though block is 0x1000 long
|
||||
assertEquals(tb.addr(0x55550000), sm.getMinTraceAddress());
|
||||
}
|
||||
|
||||
// TODO: testActionMapSectionsTo
|
||||
// TODO: testActionMapSectionTo
|
||||
|
||||
@Test
|
||||
public void testActionSelectAddresses() throws Exception {
|
||||
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
|
||||
addPlugin(tool, DebuggerListingPlugin.class);
|
||||
waitForComponentProvider(DebuggerListingProvider.class);
|
||||
// TODO: Should I hide the action if this service is missing?
|
||||
DebuggerListingService listing = tool.getService(DebuggerListingService.class);
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
addRegionsFromModules();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing(); // NOTE: The table may select first by default, enabling action
|
||||
waitForPass(
|
||||
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
|
||||
waitForPass(
|
||||
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionSelectAddresses, true);
|
||||
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x55750000, 0x5575007f)),
|
||||
new AddressSet(listing.getCurrentSelection()));
|
||||
|
||||
modulesProvider.setSelectedSections(Set.of(secExeText, secLibText));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionSelectAddresses, true);
|
||||
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x7f000000, 0x7f0003ff)),
|
||||
new AddressSet(listing.getCurrentSelection()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This action is hidden until supported")
|
||||
public void testActionCaptureTypes() throws Exception {
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
|
||||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
|
||||
// TODO: A region should not be required first. Just to get a memMapper?
|
||||
mb.testProcess1.addRegion("Memory[first_proc:.text]", mb.rng(0x55550000, 0x555500ff),
|
||||
"rx");
|
||||
TestTargetModule module =
|
||||
mb.testProcess1.modules.addModule("Modules[first_proc]",
|
||||
mb.rng(0x55550000, 0x555500ff));
|
||||
// NOTE: A section should not be required at this point.
|
||||
TestTargetTypedefDataType typedef = module.types.addTypedefDataType("myInt",
|
||||
new DefaultTargetPrimitiveDataType(PrimitiveKind.SINT, 4));
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
|
||||
traceManager.activateTrace(trace);
|
||||
waitForSwing();
|
||||
TraceModule traceModule = waitForValue(() -> recorder.getTraceModule(module));
|
||||
modulesProvider.setSelectedModules(Set.of(traceModule));
|
||||
waitForSwing();
|
||||
// TODO: When action is included, put this assertion back
|
||||
//assertTrue(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionCaptureTypes, true);
|
||||
waitForBusyTool(tool);
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// TODO: A separate action/script to transfer types from trace DTM into mapped program DTMs
|
||||
TraceBasedDataTypeManager dtm = trace.getDataTypeManager();
|
||||
TargetDataTypeConverter conv = new TargetDataTypeConverter(dtm);
|
||||
DataType expType =
|
||||
conv.convertTargetDataType(typedef).get(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
// TODO: Some heuristic or convention to extract the module name, if applicable
|
||||
waitForPass(() -> {
|
||||
DataType actType = dtm.getDataType("/Modules[first_proc].Types/myInt");
|
||||
assertTypeEquals(expType, actType);
|
||||
});
|
||||
|
||||
// TODO: When capture-types action is included, put this assertion back
|
||||
//assertTrue(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
waitForLock(trace);
|
||||
recorder.stopRecording();
|
||||
waitForSwing();
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCaptureSymbols() throws Exception {
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
|
||||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
|
||||
// TODO: A region should not be required first. Just to get a memMapper?
|
||||
mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff),
|
||||
"rx");
|
||||
TestTargetModule module =
|
||||
mb.testProcess1.modules.addModule("first_proc", mb.rng(0x55550000, 0x555500ff));
|
||||
// NOTE: A section should not be required at this point.
|
||||
module.symbols.addSymbol("test", mb.addr(0x55550080), 8,
|
||||
new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 8));
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
|
||||
traceManager.activateTrace(trace);
|
||||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
TraceModule traceModule = recorder.getTraceModule(module);
|
||||
assertNotNull(traceModule);
|
||||
modulesProvider.setSelectedModules(Set.of(traceModule));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
});
|
||||
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionCaptureSymbols, true);
|
||||
waitForBusyTool(tool);
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// TODO: A separate action/script to transfer symbols from trace into mapped programs
|
||||
// NOTE: Used types must go along.
|
||||
Collection<? extends TraceSymbol> symbols =
|
||||
trace.getSymbolManager().allSymbols().getNamed("test");
|
||||
assertEquals(1, symbols.size());
|
||||
TraceSymbol sym = symbols.iterator().next();
|
||||
// TODO: Some heuristic or convention to extract the module name, if applicable
|
||||
assertEquals("Processes[1].Modules[first_proc].Symbols::test", sym.getName(true));
|
||||
// NOTE: builder (b) is not initialized here
|
||||
assertEquals(trace.getBaseAddressFactory().getDefaultAddressSpace().getAddress(0x55550080),
|
||||
sym.getAddress());
|
||||
// TODO: Check data type once those are captured in Data units.
|
||||
|
||||
assertTrue(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
waitForLock(trace);
|
||||
recorder.stopRecording();
|
||||
waitForSwing();
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionImportFromFileSystem() throws Exception {
|
||||
addPlugin(tool, ImporterPlugin.class);
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe.setName("/bin/echo"); // File has to exist
|
||||
}
|
||||
waitForPass(
|
||||
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
performAction(modulesProvider.actionImportFromFileSystem, false);
|
||||
|
||||
GhidraFileChooser dialog = waitForDialogComponent(GhidraFileChooser.class);
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
protected Set<SectionRow> visibleSections() {
|
||||
return Set
|
||||
.copyOf(modulesProvider.legacySectionsPanel.sectionFilterPanel.getTableFilterModel()
|
||||
.getModelData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionFilterSections() throws Exception {
|
||||
addPlugin(tool, ImporterPlugin.class);
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForPass(
|
||||
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
|
||||
waitForPass(
|
||||
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
|
||||
|
||||
assertEquals(4, visibleSections().size());
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(4, visibleSections().size());
|
||||
|
||||
assertTrue(modulesProvider.actionFilterSectionsByModules.isEnabled());
|
||||
performEnabledAction(modulesProvider, modulesProvider.actionFilterSectionsByModules, true);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(2, visibleSections().size());
|
||||
for (SectionRow row : visibleSections()) {
|
||||
assertEquals(modExe, row.getModule());
|
||||
}
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of());
|
||||
waitForSwing();
|
||||
|
||||
waitForPass(() -> assertEquals(4, visibleSections().size()));
|
||||
}
|
||||
|
||||
protected static final Set<String> POPUP_ACTIONS = Set.of(AbstractSelectAddressesAction.NAME,
|
||||
DebuggerResources.NAME_MAP_MODULES, DebuggerResources.NAME_MAP_SECTIONS,
|
||||
AbstractImportFromFileSystemAction.NAME);
|
||||
|
||||
@Test
|
||||
public void testPopupActionsOnModuleSelections() throws Exception {
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
// NB. Table is debounced
|
||||
waitForPass(
|
||||
() -> assertEquals(2, modulesProvider.legacyModulesPanel.moduleTable.getRowCount()));
|
||||
|
||||
clickTableCellWithButton(modulesProvider.legacyModulesPanel.moduleTable, 0, 0,
|
||||
MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME));
|
||||
|
||||
pressEscape();
|
||||
|
||||
addPlugin(tool, ImporterPlugin.class);
|
||||
waitForSwing();
|
||||
clickTableCellWithButton(modulesProvider.legacyModulesPanel.moduleTable, 0, 0,
|
||||
MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME, AbstractImportFromFileSystemAction.NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopupActionsOnSectionSelections() throws Exception {
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForPass(
|
||||
() -> assertEquals(4, modulesProvider.legacySectionsPanel.sectionTable.getRowCount()));
|
||||
|
||||
clickTableCellWithButton(modulesProvider.legacySectionsPanel.sectionTable, 0, 0,
|
||||
MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME));
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.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.trace.model.Trace;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
@Category(NightlyCategory.class)
|
||||
public class DebuggerModulesProviderObjectTest extends DebuggerModulesProviderTest {
|
||||
|
||||
protected SchemaContext ctx;
|
||||
|
||||
@Override
|
||||
protected void createTrace(String langID) throws IOException {
|
||||
super.createTrace(langID);
|
||||
try {
|
||||
activateObjectsMode();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void useTrace(Trace trace) {
|
||||
super.useTrace(trace);
|
||||
try {
|
||||
activateObjectsMode();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void activateObjectsMode() throws Exception {
|
||||
// NOTE the use of index='1' allowing object-based managers to ID unique path
|
||||
ctx = XmlSchemaContext.deserialize("" + //
|
||||
"<context>" + //
|
||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Processes' schema='ProcessContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element index='1' schema='Process' />" + // <---- NOTE HERE
|
||||
" </schema>" + //
|
||||
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Modules' schema='ModuleContainer' />" + //
|
||||
" <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>" + //
|
||||
" <schema name='ModuleContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Module' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Module' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Module' />" + //
|
||||
" <attribute name='Sections' schema='SectionContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='SectionContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Section' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Section' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Section' />" + //
|
||||
" </schema>" + //
|
||||
"</context>");
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.modules;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -25,111 +26,234 @@ import org.junit.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.table.DynamicTableColumn;
|
||||
import generic.Unique;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog.MemoryBlockRow;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractImportFromFileSystemAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
|
||||
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.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.plugin.core.debug.gui.modules.DebuggerModuleMapProposalDialog.ModuleMapTableColumns;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapModulesAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider.MapSectionsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerSectionMapProposalDialog.SectionMapTableColumns;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.app.services.SectionMapProposal.SectionMapEntry;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
|
||||
import ghidra.dbg.model.TestTargetModule;
|
||||
import ghidra.dbg.model.TestTargetTypedefDataType;
|
||||
import ghidra.dbg.util.TargetDataTypeConverter;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.plugin.importer.ImporterPlugin;
|
||||
import ghidra.program.model.address.AddressOverflowException;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.CompilerSpecID;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.mem.MemoryConflictException;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.module.TraceObjectSection;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.data.TraceBasedDataTypeManager;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.memory.TraceOverlappedRegionException;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.trace.model.symbol.TraceSymbol;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
||||
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
|
||||
@Category(NightlyCategory.class)
|
||||
public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
protected DebuggerModulesPlugin modulesPlugin;
|
||||
protected DebuggerModulesProvider modulesProvider;
|
||||
|
||||
protected TraceModule modExe;
|
||||
protected TraceSection secExeText;
|
||||
protected TraceSection secExeData;
|
||||
DebuggerModulesProvider provider;
|
||||
|
||||
protected TraceModule modLib;
|
||||
protected TraceSection secLibText;
|
||||
protected TraceSection secLibData;
|
||||
protected TraceObjectModule modExe;
|
||||
protected TraceObjectSection secExeText;
|
||||
protected TraceObjectSection secExeData;
|
||||
|
||||
@Before
|
||||
public void setUpModulesProviderTest() throws Exception {
|
||||
modulesPlugin = addPlugin(tool, DebuggerModulesPlugin.class);
|
||||
modulesProvider = waitForComponentProvider(DebuggerModulesProvider.class);
|
||||
protected TraceObjectModule modLib;
|
||||
protected TraceObjectSection secLibText;
|
||||
protected TraceObjectSection secLibData;
|
||||
|
||||
protected SchemaContext ctx;
|
||||
|
||||
@Override
|
||||
protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target)
|
||||
throws Exception {
|
||||
return new ObjectBasedDebuggerTargetTraceMapper(target,
|
||||
new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of());
|
||||
}
|
||||
|
||||
protected void addRegionsFromModules()
|
||||
throws TraceOverlappedRegionException, DuplicateNameException {
|
||||
@Override
|
||||
protected TraceRecorder recordAndWaitSync() throws Throwable {
|
||||
TraceRecorder recorder = super.recordAndWaitSync();
|
||||
useTrace(recorder.getTrace());
|
||||
return recorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TargetObject chooseTarget() {
|
||||
return mb.testModel.session;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createTrace(String langID) throws IOException {
|
||||
super.createTrace(langID);
|
||||
try {
|
||||
activateObjectsMode();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void useTrace(Trace trace) {
|
||||
super.useTrace(trace);
|
||||
if (trace.getObjectManager().getRootObject() != null) {
|
||||
// If live, recorder will have created it
|
||||
return;
|
||||
}
|
||||
try {
|
||||
activateObjectsMode();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void activateObjectsMode() throws Exception {
|
||||
// NOTE the use of index='1' allowing object-based managers to ID unique path
|
||||
ctx = XmlSchemaContext.deserialize("" + //
|
||||
"<context>" + //
|
||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Processes' schema='ProcessContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element index='1' schema='Process' />" + // <---- NOTE HERE
|
||||
" </schema>" + //
|
||||
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Modules' schema='ModuleContainer' />" + //
|
||||
" <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>" + //
|
||||
" <schema name='ModuleContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Module' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Module' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Module' />" + //
|
||||
" <attribute name='Sections' schema='SectionContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='SectionContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Section' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Section' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Section' />" + //
|
||||
" </schema>" + //
|
||||
"</context>");
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
DBTraceMemoryManager manager = tb.trace.getMemoryManager();
|
||||
for (TraceModule module : tb.trace.getModuleManager().getAllModules()) {
|
||||
for (TraceSection section : module.getSections()) {
|
||||
Set<TraceMemoryFlag> flags = new HashSet<>();
|
||||
flags.add(TraceMemoryFlag.READ);
|
||||
if (".text".equals(section.getName())) {
|
||||
flags.add(TraceMemoryFlag.EXECUTE);
|
||||
}
|
||||
else if (".data".equals(section.getName())) {
|
||||
flags.add(TraceMemoryFlag.WRITE);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
manager.addRegion(
|
||||
"Processes[1].Memory[" + module.getName() + ":" + section.getName() + "]",
|
||||
module.getLifespan(), section.getRange(), flags);
|
||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||
}
|
||||
}
|
||||
|
||||
protected void addRegionsFromModules() throws Exception {
|
||||
PathPattern regionPattern = new PathPattern(PathUtils.parse("Processes[1].Memory[]"));
|
||||
TraceObjectManager om = tb.trace.getObjectManager();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
TraceObject root = om.getRootObject();
|
||||
for (TraceObject module : (Iterable<TraceObject>) () -> root
|
||||
.querySuccessorsTargetInterface(Lifespan.at(0), TargetModule.class)
|
||||
.map(p -> p.getDestination(root))
|
||||
.iterator()) {
|
||||
String moduleName = module.getCanonicalPath().index();
|
||||
Lifespan span = module.getLife().bound();
|
||||
for (TraceObject section : (Iterable<TraceObject>) () -> module
|
||||
.querySuccessorsTargetInterface(Lifespan.at(0), TargetSection.class)
|
||||
.map(p -> p.getDestination(root))
|
||||
.iterator()) {
|
||||
String sectionName = section.getCanonicalPath().index();
|
||||
TraceObject region = om.createObject(TraceObjectKeyPath
|
||||
.of(regionPattern.applyKeys(moduleName + ":" + sectionName)
|
||||
.getSingletonPath()))
|
||||
.insert(span, ConflictResolution.TRUNCATE)
|
||||
.getDestination(root);
|
||||
region.setAttribute(span, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME,
|
||||
section.getAttribute(0, TargetSection.RANGE_ATTRIBUTE_NAME).getValue());
|
||||
region.setAttribute(span, TargetMemoryRegion.READABLE_ATTRIBUTE_NAME, true);
|
||||
region.setAttribute(span, TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME,
|
||||
".data".equals(sectionName));
|
||||
region.setAttribute(span, TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME,
|
||||
".text".equals(sectionName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void addModules() throws Exception {
|
||||
TraceModuleManager manager = tb.trace.getModuleManager();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe = manager.addLoadedModule("Processes[1].Modules[first_proc]", "first_proc",
|
||||
tb.range(0x55550000, 0x5575007f), 0);
|
||||
secExeText = modExe.addSection("Processes[1].Modules[first_proc].Sections[.text]",
|
||||
".text", tb.range(0x55550000, 0x555500ff));
|
||||
secExeData = modExe.addSection("Processes[1].Modules[first_proc].Sections[.data]",
|
||||
".data", tb.range(0x55750000, 0x5575007f));
|
||||
protected TraceObjectModule addModule(String name, AddressRange range, Lifespan span) {
|
||||
PathPattern modulePattern = new PathPattern(PathUtils.parse("Processes[1].Modules[]"));
|
||||
TraceObjectManager om = tb.trace.getObjectManager();
|
||||
TraceObjectModule module = Objects.requireNonNull(
|
||||
om.createObject(TraceObjectKeyPath.of(modulePattern.applyKeys(name).getSingletonPath()))
|
||||
.insert(span, ConflictResolution.TRUNCATE)
|
||||
.getDestination(null)
|
||||
.queryInterface(TraceObjectModule.class));
|
||||
module.getObject().setAttribute(span, TargetModule.MODULE_NAME_ATTRIBUTE_NAME, name);
|
||||
module.getObject().setAttribute(span, TargetModule.RANGE_ATTRIBUTE_NAME, range);
|
||||
return module;
|
||||
}
|
||||
|
||||
modLib = manager.addLoadedModule("Processes[1].Modules[some_lib]", "some_lib",
|
||||
tb.range(0x7f000000, 0x7f10003f), 0);
|
||||
secLibText = modLib.addSection("Processes[1].Modules[some_lib].Sections[.text]",
|
||||
".text", tb.range(0x7f000000, 0x7f0003ff));
|
||||
secLibData = modLib.addSection("Processes[1].Modules[some_lib].Sections[.data]",
|
||||
".data", tb.range(0x7f100000, 0x7f10003f));
|
||||
protected TraceObjectSection addSection(TraceObjectModule module, String name,
|
||||
AddressRange range) {
|
||||
TraceObjectManager om = tb.trace.getObjectManager();
|
||||
Lifespan span = module.getObject().getLife().bound();
|
||||
TraceObjectSection section = Objects.requireNonNull(om
|
||||
.createObject(
|
||||
module.getObject().getCanonicalPath().key("Sections").index(name))
|
||||
.insert(span, ConflictResolution.TRUNCATE)
|
||||
.getDestination(null)
|
||||
.queryInterface(TraceObjectSection.class));
|
||||
section.getObject().setAttribute(span, TargetSection.RANGE_ATTRIBUTE_NAME, range);
|
||||
return section;
|
||||
}
|
||||
|
||||
protected void addModules() throws Exception {
|
||||
Lifespan zeroOn = Lifespan.nowOn(0);
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe = addModule("first_proc", tb.range(0x55550000, 0x5575007f), zeroOn);
|
||||
secExeText = addSection(modExe, ".text", tb.range(0x55550000, 0x555500ff));
|
||||
secExeData = addSection(modExe, ".data", tb.range(0x55750000, 0x5575007f));
|
||||
|
||||
modLib = addModule("some_lib", tb.range(0x7f000000, 0x7f10003f), zeroOn);
|
||||
secLibText = addSection(modLib, ".text", tb.range(0x7f000000, 0x7f0003ff));
|
||||
secLibData = addSection(modLib, ".data", tb.range(0x7f100000, 0x7f10003f));
|
||||
}
|
||||
}
|
||||
|
||||
protected MemoryBlock addBlock() throws Exception,
|
||||
MemoryConflictException, AddressOverflowException, CancelledException {
|
||||
protected MemoryBlock addBlock() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
|
||||
return program.getMemory()
|
||||
.createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, monitor,
|
||||
@ -137,67 +261,108 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertProviderEmpty() {
|
||||
List<ModuleRow> modulesDisplayed = modulesProvider.moduleTableModel.getModelData();
|
||||
assertTrue(modulesDisplayed.isEmpty());
|
||||
protected void assertModuleTableSize(int size) {
|
||||
assertEquals(size, provider.modulesPanel.getAllItems().size());
|
||||
}
|
||||
|
||||
List<SectionRow> sectionsDisplayed = modulesProvider.sectionTableModel.getModelData();
|
||||
assertTrue(sectionsDisplayed.isEmpty());
|
||||
protected void assertSectionTableSize(int size) {
|
||||
assertEquals(size, provider.sectionsPanel.getAllItems().size());
|
||||
}
|
||||
|
||||
protected void assertProviderEmpty() {
|
||||
assertModuleTableSize(0);
|
||||
assertSectionTableSize(0);
|
||||
}
|
||||
|
||||
protected void assertModuleRow(int pos, Object object, String name, Address start, Address end,
|
||||
long length) {
|
||||
ValueRow row = provider.modulesPanel.getAllItems().get(pos);
|
||||
DynamicTableColumn<ValueRow, ?, Trace> nameCol =
|
||||
provider.modulesPanel.getColumnByNameAndType("Name", ValueProperty.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> baseCol =
|
||||
provider.modulesPanel.getColumnByNameAndType("Base", ValueProperty.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> maxCol =
|
||||
provider.modulesPanel.getColumnByNameAndType("Max", ValueProperty.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> lengthCol =
|
||||
provider.modulesPanel.getColumnByNameAndType("Length", ValueProperty.class).getValue();
|
||||
|
||||
assertSame(object, row.getValue().getValue());
|
||||
assertEquals(name, rowColVal(row, nameCol));
|
||||
assertEquals(start, rowColVal(row, baseCol));
|
||||
assertEquals(end, rowColVal(row, maxCol));
|
||||
assertEquals(length, rowColVal(row, lengthCol));
|
||||
}
|
||||
|
||||
protected void assertSectionRow(int pos, Object object, String moduleName, String name,
|
||||
Address start, Address end, long length) {
|
||||
ValueRow row = provider.sectionsPanel.getAllItems().get(pos);
|
||||
DynamicTableColumn<ValueRow, ?, Trace> moduleNameCol =
|
||||
provider.sectionsPanel.getColumnByNameAndType("Module Name", ValueProperty.class)
|
||||
.getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> nameCol =
|
||||
provider.sectionsPanel.getColumnByNameAndType("Name", String.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> startCol =
|
||||
provider.sectionsPanel.getColumnByNameAndType("Start", ValueProperty.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> endCol =
|
||||
provider.sectionsPanel.getColumnByNameAndType("End", ValueProperty.class).getValue();
|
||||
DynamicTableColumn<ValueRow, ?, Trace> lengthCol =
|
||||
provider.sectionsPanel.getColumnByNameAndType("Length", ValueProperty.class).getValue();
|
||||
|
||||
assertSame(object, row.getValue().getValue());
|
||||
assertEquals(moduleName, rowColVal(row, moduleNameCol));
|
||||
assertEquals(name, rowColVal(row, nameCol));
|
||||
assertEquals(start, rowColVal(row, startCol));
|
||||
assertEquals(end, rowColVal(row, endCol));
|
||||
assertEquals(length, rowColVal(row, lengthCol));
|
||||
}
|
||||
|
||||
protected void assertProviderPopulated() {
|
||||
List<ModuleRow> modulesDisplayed =
|
||||
new ArrayList<>(modulesProvider.moduleTableModel.getModelData());
|
||||
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
|
||||
// I should be able to assume this is sorted by base address. It's the default sort column.
|
||||
assertEquals(2, modulesDisplayed.size());
|
||||
assertModuleTableSize(2);
|
||||
assertSectionTableSize(4);
|
||||
|
||||
ModuleRow execRow = modulesDisplayed.get(0);
|
||||
assertEquals(tb.addr(0x55550000), execRow.getBase());
|
||||
assertEquals("first_proc", execRow.getName());
|
||||
assertModuleRow(0, modExe.getObject(), "first_proc", tb.addr(0x55550000),
|
||||
tb.addr(0x5575007f), 0x00200080);
|
||||
assertSectionRow(0, secExeText.getObject(), "first_proc", ".text", tb.addr(0x55550000),
|
||||
tb.addr(0x555500ff), 256);
|
||||
assertSectionRow(1, secExeData.getObject(), "first_proc", ".data", tb.addr(0x55750000),
|
||||
tb.addr(0x5575007f), 128);
|
||||
|
||||
// Use only (start) offset for excess, as unique ID
|
||||
ModuleRow libRow = modulesDisplayed.get(1);
|
||||
assertEquals(tb.addr(0x7f000000), libRow.getBase());
|
||||
assertModuleRow(1, modLib.getObject(), "some_lib", tb.addr(0x7f000000),
|
||||
tb.addr(0x7f10003f), 0x00100040);
|
||||
assertSectionRow(2, secLibText.getObject(), "some_lib", ".text", tb.addr(0x7f000000),
|
||||
tb.addr(0x7f0003ff), 1024);
|
||||
assertSectionRow(3, secLibData.getObject(), "some_lib", ".data", tb.addr(0x7f100000),
|
||||
tb.addr(0x7f10003f), 64);
|
||||
}
|
||||
|
||||
List<SectionRow> sectionsDisplayed =
|
||||
new ArrayList<>(modulesProvider.sectionTableModel.getModelData());
|
||||
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
|
||||
assertEquals(4, sectionsDisplayed.size());
|
||||
@Before
|
||||
public void setUpModulesProviderTest() throws Exception {
|
||||
addPlugin(tool, DebuggerModulesPlugin.class);
|
||||
provider = waitForComponentProvider(DebuggerModulesProvider.class);
|
||||
}
|
||||
|
||||
SectionRow execTextRow = sectionsDisplayed.get(0);
|
||||
assertEquals(tb.addr(0x55550000), execTextRow.getStart());
|
||||
assertEquals(tb.addr(0x555500ff), execTextRow.getEnd());
|
||||
assertEquals("first_proc", execTextRow.getModuleName());
|
||||
assertEquals(".text", execTextRow.getName());
|
||||
assertEquals(256, execTextRow.getLength());
|
||||
|
||||
SectionRow execDataRow = sectionsDisplayed.get(1);
|
||||
assertEquals(tb.addr(0x55750000), execDataRow.getStart());
|
||||
|
||||
SectionRow libTextRow = sectionsDisplayed.get(2);
|
||||
assertEquals(tb.addr(0x7f000000), libTextRow.getStart());
|
||||
|
||||
SectionRow libDataRow = sectionsDisplayed.get(3);
|
||||
assertEquals(tb.addr(0x7f100000), libDataRow.getStart());
|
||||
@After
|
||||
public void tearDownModulesProviderTest() throws Exception {
|
||||
traceManager.activate(DebuggerCoordinates.NOWHERE);
|
||||
waitForTasks();
|
||||
runSwing(() -> traceManager.closeAllTraces());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() throws Exception {
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThenAddModulesPopulatesProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
addModules();
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated();
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -205,14 +370,14 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
createAndOpenTrace();
|
||||
|
||||
addModules();
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderEmpty();
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated();
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -224,17 +389,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
MemoryBlock block = addBlock();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setName(modExe.getName());
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
|
||||
waitForPass(() -> assertSectionTableSize(4));
|
||||
|
||||
runSwing(() -> modulesProvider.setSelectedSections(Set.of(secExeText)));
|
||||
performAction(modulesProvider.actionMapSections, false);
|
||||
runSwing(() -> provider.setSelectedSections(Set.of(secExeText)));
|
||||
performEnabledAction(provider, provider.actionMapSections, false);
|
||||
|
||||
DebuggerSectionMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||
@ -258,35 +423,27 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe.delete();
|
||||
modExe.getObject().removeTree(Lifespan.nowOn(0));
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
List<ModuleRow> modulesDisplayed =
|
||||
new ArrayList<>(modulesProvider.moduleTableModel.getModelData());
|
||||
modulesDisplayed.sort(Comparator.comparing(r -> r.getBase()));
|
||||
assertEquals(1, modulesDisplayed.size());
|
||||
waitForPass(() -> {
|
||||
assertModuleTableSize(1);
|
||||
assertSectionTableSize(2);
|
||||
|
||||
ModuleRow libRow = modulesDisplayed.get(0);
|
||||
assertEquals("some_lib", libRow.getName());
|
||||
|
||||
List<SectionRow> sectionsDisplayed =
|
||||
new ArrayList<>(modulesProvider.sectionTableModel.getModelData());
|
||||
sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart()));
|
||||
assertEquals(2, sectionsDisplayed.size());
|
||||
|
||||
SectionRow libTextRow = sectionsDisplayed.get(0);
|
||||
assertEquals(".text", libTextRow.getName());
|
||||
assertEquals("some_lib", libTextRow.getModuleName());
|
||||
|
||||
SectionRow libDataRow = sectionsDisplayed.get(1);
|
||||
assertEquals(".data", libDataRow.getName());
|
||||
assertEquals("some_lib", libDataRow.getModuleName());
|
||||
assertModuleRow(0, modLib.getObject(), "some_lib", tb.addr(0x7f000000),
|
||||
tb.addr(0x7f10003f), 0x00100040);
|
||||
assertSectionRow(0, secLibText.getObject(), "some_lib", ".text", tb.addr(0x7f000000),
|
||||
tb.addr(0x7f0003ff), 1024);
|
||||
assertSectionRow(1, secLibData.getObject(), "some_lib", ".data", tb.addr(0x7f100000),
|
||||
tb.addr(0x7f10003f), 64);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -295,15 +452,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
undo(tb.trace);
|
||||
assertProviderEmpty();
|
||||
waitForDomainObject(tb.trace);
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
|
||||
redo(tb.trace);
|
||||
assertProviderPopulated();
|
||||
waitForDomainObject(tb.trace);
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -312,17 +471,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
traceManager.activateTrace(null);
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertProviderPopulated();
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -331,18 +490,18 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertProviderPopulated(); // Cheap sanity check
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
traceManager.closeTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertProviderEmpty();
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertProviderEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionMapIdentically() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapIdentically.isEnabled());
|
||||
assertFalse(provider.actionMapIdentically.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
@ -351,9 +510,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
// No modules necessary
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
assertTrue(modulesProvider.actionMapIdentically.isEnabled());
|
||||
waitForPass(() -> assertTrue(provider.actionMapIdentically.isEnabled()));
|
||||
|
||||
// Need some substance in the program
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate")) {
|
||||
@ -361,7 +520,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
|
||||
performAction(modulesProvider.actionMapIdentically);
|
||||
performEnabledAction(provider, provider.actionMapIdentically, true);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
Collection<? extends TraceStaticMapping> mappings =
|
||||
@ -377,7 +536,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
@Test
|
||||
public void testActionMapModules() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapModules.isEnabled());
|
||||
assertFalse(provider.actionMapModules.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
@ -389,7 +548,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
waitForSwing();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionMapModules.isEnabled());
|
||||
assertFalse(provider.actionMapModules.isEnabled());
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setImageBase(addr(program, 0x00400000), true);
|
||||
@ -398,13 +557,13 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
addBlock(); // So the program has a size
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertModuleTableSize(2));
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionMapModules.isEnabled());
|
||||
runSwing(() -> provider.setSelectedModules(Set.of(modExe)));
|
||||
assertTrue(provider.actionMapModules.isEnabled());
|
||||
|
||||
performAction(modulesProvider.actionMapModules, false);
|
||||
performEnabledAction(provider, provider.actionMapModules, false);
|
||||
|
||||
DebuggerModuleMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerModuleMapProposalDialog.class);
|
||||
@ -444,7 +603,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
@Test
|
||||
public void testActionMapSections() throws Exception {
|
||||
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
||||
assertFalse(provider.actionMapSections.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
createAndOpenProgramFromTrace();
|
||||
@ -453,23 +612,23 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionMapSections.isEnabled());
|
||||
assertFalse(provider.actionMapSections.isEnabled());
|
||||
|
||||
MemoryBlock block = addBlock();
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
|
||||
program.setName(modExe.getName());
|
||||
}
|
||||
waitForDomainObject(program);
|
||||
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertSectionTableSize(4));
|
||||
|
||||
modulesProvider.setSelectedSections(Set.of(secExeText));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionMapSections.isEnabled());
|
||||
runSwing(() -> provider.setSelectedSections(Set.of(secExeText)));
|
||||
assertTrue(provider.actionMapSections.isEnabled());
|
||||
|
||||
performAction(modulesProvider.actionMapSections, false);
|
||||
performEnabledAction(provider, provider.actionMapSections, false);
|
||||
|
||||
DebuggerSectionMapProposalDialog propDialog =
|
||||
waitForDialogComponent(DebuggerSectionMapProposalDialog.class);
|
||||
@ -510,7 +669,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
@Test
|
||||
public void testActionSelectAddresses() throws Exception {
|
||||
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
assertFalse(provider.actionSelectAddresses.isEnabled());
|
||||
|
||||
addPlugin(tool, DebuggerListingPlugin.class);
|
||||
waitForComponentProvider(DebuggerListingProvider.class);
|
||||
@ -522,25 +681,20 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
addRegionsFromModules();
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
assertFalse(provider.actionSelectAddresses.isEnabled());
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing(); // NOTE: The table may select first by default, enabling action
|
||||
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
|
||||
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
waitForTasks(); // NOTE: The table may select first by default, enabling action
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
runSwing(() -> provider.setSelectedModules(Set.of(modExe)));
|
||||
|
||||
performAction(modulesProvider.actionSelectAddresses);
|
||||
performEnabledAction(provider, provider.actionSelectAddresses, true);
|
||||
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x55750000, 0x5575007f)),
|
||||
new AddressSet(listing.getCurrentSelection()));
|
||||
|
||||
modulesProvider.setSelectedSections(Set.of(secExeText, secLibText));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
|
||||
runSwing(() -> provider.setSelectedSections(Set.of(secExeText, secLibText)));
|
||||
|
||||
performAction(modulesProvider.actionSelectAddresses);
|
||||
performEnabledAction(provider, provider.actionSelectAddresses, true);
|
||||
assertEquals(tb.set(tb.range(0x55550000, 0x555500ff), tb.range(0x7f000000, 0x7f0003ff)),
|
||||
new AddressSet(listing.getCurrentSelection()));
|
||||
}
|
||||
@ -548,7 +702,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
@Test
|
||||
@Ignore("This action is hidden until supported")
|
||||
public void testActionCaptureTypes() throws Exception {
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
assertFalse(provider.actionCaptureTypes.isEnabled());
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
@ -568,17 +722,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
waitForDomainObject(trace);
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
assertFalse(provider.actionCaptureTypes.isEnabled());
|
||||
|
||||
traceManager.activateTrace(trace);
|
||||
waitForSwing();
|
||||
TraceModule traceModule = waitForValue(() -> recorder.getTraceModule(module));
|
||||
modulesProvider.setSelectedModules(Set.of(traceModule));
|
||||
provider.setSelectedModules(Set.of(traceModule));
|
||||
waitForSwing();
|
||||
// TODO: When action is included, put this assertion back
|
||||
//assertTrue(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
|
||||
performAction(modulesProvider.actionCaptureTypes, true);
|
||||
performEnabledAction(provider, provider.actionCaptureTypes, true);
|
||||
waitForBusyTool(tool);
|
||||
waitForDomainObject(trace);
|
||||
|
||||
@ -598,18 +752,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
waitForLock(trace);
|
||||
recorder.stopRecording();
|
||||
waitForSwing();
|
||||
assertFalse(modulesProvider.actionCaptureTypes.isEnabled());
|
||||
assertFalse(provider.actionCaptureTypes.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionCaptureSymbols() throws Exception {
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
public void testActionCaptureSymbols() throws Throwable {
|
||||
assertFalse(provider.actionCaptureSymbols.isEnabled());
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
|
||||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
|
||||
// TODO: A region should not be required first. Just to get a memMapper?
|
||||
mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff),
|
||||
@ -619,43 +772,43 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
// NOTE: A section should not be required at this point.
|
||||
module.symbols.addSymbol("test", mb.addr(0x55550080), 8,
|
||||
new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 8));
|
||||
waitForDomainObject(trace);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
// Still
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
assertFalse(provider.actionCaptureSymbols.isEnabled());
|
||||
|
||||
traceManager.activateTrace(trace);
|
||||
waitForSwing();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForTasks();
|
||||
waitForPass(() -> {
|
||||
TraceModule traceModule = recorder.getTraceModule(module);
|
||||
assertNotNull(traceModule);
|
||||
modulesProvider.setSelectedModules(Set.of(traceModule));
|
||||
waitForSwing();
|
||||
assertTrue(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
runSwing(() -> provider.setSelectedModules(Set.of(traceModule)));
|
||||
assertTrue(provider.actionCaptureSymbols.isEnabled());
|
||||
});
|
||||
|
||||
performAction(modulesProvider.actionCaptureSymbols, true);
|
||||
performEnabledAction(provider, provider.actionCaptureSymbols, true);
|
||||
waitForBusyTool(tool);
|
||||
waitForDomainObject(trace);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
// TODO: A separate action/script to transfer symbols from trace into mapped programs
|
||||
// TODO: Let this action work on the TraceObjects instead of TargetObjects
|
||||
// NOTE: Used types must go along.
|
||||
Collection<? extends TraceSymbol> symbols =
|
||||
trace.getSymbolManager().allSymbols().getNamed("test");
|
||||
tb.trace.getSymbolManager().allSymbols().getNamed("test");
|
||||
assertEquals(1, symbols.size());
|
||||
TraceSymbol sym = symbols.iterator().next();
|
||||
// TODO: Some heuristic or convention to extract the module name, if applicable
|
||||
assertEquals("Processes[1].Modules[first_proc].Symbols::test", sym.getName(true));
|
||||
// NOTE: builder (b) is not initialized here
|
||||
assertEquals(trace.getBaseAddressFactory().getDefaultAddressSpace().getAddress(0x55550080),
|
||||
assertEquals(tb.addr(0x55550080),
|
||||
sym.getAddress());
|
||||
// TODO: Check data type once those are captured in Data units.
|
||||
|
||||
assertTrue(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
waitForLock(trace);
|
||||
assertTrue(provider.actionCaptureSymbols.isEnabled());
|
||||
waitForLock(tb.trace);
|
||||
recorder.stopRecording();
|
||||
waitForSwing();
|
||||
assertFalse(modulesProvider.actionCaptureSymbols.isEnabled());
|
||||
assertFalse(provider.actionCaptureSymbols.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -664,53 +817,53 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForTasks();
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
modExe.setName("/bin/echo"); // File has to exist
|
||||
}
|
||||
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
|
||||
waitForPass(() -> assertModuleTableSize(2));
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
performAction(modulesProvider.actionImportFromFileSystem, false);
|
||||
runSwing(() -> provider.setSelectedModules(Set.of(modExe)));
|
||||
performAction(provider.actionImportFromFileSystem, false);
|
||||
|
||||
GhidraFileChooser dialog = waitForDialogComponent(GhidraFileChooser.class);
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
protected Set<SectionRow> visibleSections() {
|
||||
return Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
|
||||
protected Set<ValueRow> visibleSections() {
|
||||
return Set.copyOf(QueryPanelTestHelper.getFilterPanel(provider.sectionsPanel)
|
||||
.getTableFilterModel()
|
||||
.getModelData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionFilterSections() throws Exception {
|
||||
addPlugin(tool, ImporterPlugin.class);
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
|
||||
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
assertEquals(4, visibleSections().size());
|
||||
waitForPass(() -> assertEquals(4, visibleSections().size()));
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of(modExe));
|
||||
waitForSwing();
|
||||
runSwing(() -> provider.setSelectedModules(Set.of(modExe)));
|
||||
|
||||
assertEquals(4, visibleSections().size());
|
||||
waitForPass(() -> assertEquals(4, visibleSections().size()));
|
||||
|
||||
assertTrue(modulesProvider.actionFilterSectionsByModules.isEnabled());
|
||||
performAction(modulesProvider.actionFilterSectionsByModules);
|
||||
waitForSwing();
|
||||
performEnabledAction(provider, provider.actionFilterSectionsByModules, true);
|
||||
waitForTasks();
|
||||
|
||||
assertEquals(2, visibleSections().size());
|
||||
for (SectionRow row : visibleSections()) {
|
||||
assertEquals(modExe, row.getModule());
|
||||
waitForPass(() -> assertEquals(2, visibleSections().size()));
|
||||
for (ValueRow row : visibleSections()) {
|
||||
assertEquals(modExe.getObject(), row.getValue()
|
||||
.getChild()
|
||||
.queryCanonicalAncestorsTargetInterface(TargetModule.class)
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
modulesProvider.setSelectedModules(Set.of());
|
||||
waitForSwing();
|
||||
runSwing(() -> provider.setSelectedModules(Set.of()));
|
||||
|
||||
waitForPass(() -> assertEquals(4, visibleSections().size()));
|
||||
}
|
||||
@ -724,11 +877,11 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
// NB. Table is debounced
|
||||
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertModuleTableSize(2));
|
||||
|
||||
clickTableCellWithButton(modulesProvider.moduleTable, 0, 0, MouseEvent.BUTTON3);
|
||||
GhidraTable moduleTable = QueryPanelTestHelper.getTable(provider.modulesPanel);
|
||||
clickTableCellWithButton(moduleTable, 0, 0, MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME));
|
||||
@ -737,7 +890,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
|
||||
addPlugin(tool, ImporterPlugin.class);
|
||||
waitForSwing();
|
||||
clickTableCellWithButton(modulesProvider.moduleTable, 0, 0, MouseEvent.BUTTON3);
|
||||
clickTableCellWithButton(moduleTable, 0, 0, MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME, AbstractImportFromFileSystemAction.NAME));
|
||||
@ -748,10 +901,11 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
createAndOpenTrace();
|
||||
addModules();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertSectionTableSize(4));
|
||||
|
||||
clickTableCellWithButton(modulesProvider.sectionTable, 0, 0, MouseEvent.BUTTON3);
|
||||
GhidraTable sectionTable = QueryPanelTestHelper.getTable(provider.sectionsPanel);
|
||||
clickTableCellWithButton(sectionTable, 0, 0, MouseEvent.BUTTON3);
|
||||
waitForSwing();
|
||||
assertMenu(POPUP_ACTIONS, Set.of(MapModulesAction.NAME, MapSectionsAction.NAME,
|
||||
AbstractSelectAddressesAction.NAME));
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.dbg.attributes;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface TargetArrayDataType extends TargetDataType {
|
||||
public class DefaultTargetArrayDataType implements TargetArrayDataType {
|
||||
protected final TargetDataType elementType;
|
||||
@ -34,6 +37,14 @@ public interface TargetArrayDataType extends TargetDataType {
|
||||
public int getElementCount() {
|
||||
return elementCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.add("elementType", elementType.toJson());
|
||||
object.addProperty("elementCount", elementCount);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
TargetDataType getElementType();
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.dbg.attributes;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* A bitfield-modified data type
|
||||
*
|
||||
@ -47,6 +50,15 @@ public interface TargetBitfieldDataType extends TargetDataType {
|
||||
public int getBitLength() {
|
||||
return bitLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.add("fieldType", fieldType.toJson());
|
||||
object.addProperty("leastBitPosition", leastBitPosition);
|
||||
object.addProperty("bitLength", bitLength);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
TargetDataType getFieldType();
|
||||
|
@ -15,10 +15,14 @@
|
||||
*/
|
||||
package ghidra.dbg.attributes;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
|
||||
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
|
||||
|
||||
public interface TargetDataType {
|
||||
TargetDataType UNDEFINED1 =
|
||||
new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 1);
|
||||
|
||||
JsonElement toJson();
|
||||
}
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.dbg.attributes;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface TargetPointerDataType extends TargetDataType {
|
||||
public class DefaultTargetPointerDataType implements TargetPointerDataType {
|
||||
protected final TargetDataType referentType;
|
||||
@ -27,6 +30,13 @@ public interface TargetPointerDataType extends TargetDataType {
|
||||
public TargetDataType getReferentType() {
|
||||
return referentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJson() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.add("referentType", referentType.toJson());
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
TargetDataType getReferentType();
|
||||
|
@ -15,13 +15,14 @@
|
||||
*/
|
||||
package ghidra.dbg.attributes;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
public interface TargetPrimitiveDataType extends TargetDataType {
|
||||
public static final TargetDataType VOID =
|
||||
new DefaultTargetPrimitiveDataType(PrimitiveKind.VOID, 0);
|
||||
|
||||
enum PrimitiveKind {
|
||||
UNDEFINED,
|
||||
@SuppressWarnings("hiding")
|
||||
VOID,
|
||||
UINT,
|
||||
SINT,
|
||||
@ -47,6 +48,14 @@ public interface TargetPrimitiveDataType extends TargetDataType {
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJson() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("kind", kind.toString());
|
||||
object.addProperty("length", length);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
PrimitiveKind getKind();
|
||||
|
@ -18,6 +18,9 @@ package ghidra.dbg.target;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebuggerTargetObjectIface;
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
@ -57,6 +60,13 @@ public interface TargetNamedDataType extends TargetObject, TargetDataType {
|
||||
String FUNCTION_RETURN_INDEX = "return";
|
||||
String FUNCTION_PARAMETER_DIM = "param";
|
||||
|
||||
@Override
|
||||
default JsonElement toJson() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("path", getJoinedPath("."));
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the members of this data type in order.
|
||||
*
|
||||
|
@ -18,6 +18,7 @@ package ghidra.dbg.target.schema;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
@ -419,6 +420,39 @@ public interface TargetObjectSchema {
|
||||
return childSchema.getSuccessorSchema(path.subList(1, path.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of schemas traversed from this schema along the given (sub) path
|
||||
*
|
||||
* <p>
|
||||
* This list always begins with this schema, followed by the child schema for each key in the
|
||||
* path. Thus, for a path of length n, the resulting list has n+1 entries. This is useful for
|
||||
* searches along the ancestry of a given path:
|
||||
*
|
||||
* <pre>
|
||||
* List<TargetObjectSchema> schemas = getSuccessorSchemas(path);
|
||||
* for (; path != null; path = PathUtils.parent(path)) {
|
||||
* TargetObjectSchema schema = schemas.get(path.size());
|
||||
* // ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* All entries are non-null, though they may be {@link EnumerableTargetObjectSchema#VOID}.
|
||||
*
|
||||
* @param path the relative path from an object having this schema to the desired successor
|
||||
* @return the list of schemas traversed, ending with the successor's schema
|
||||
*/
|
||||
default List<TargetObjectSchema> getSuccessorSchemas(List<String> path) {
|
||||
List<TargetObjectSchema> result = new ArrayList<>();
|
||||
TargetObjectSchema schema = this;
|
||||
result.add(schema);
|
||||
for (String key : path) {
|
||||
schema = schema.getChildSchema(key);
|
||||
result.add(schema);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the same as {@link #searchFor(Class, List, boolean)} with an empty prefix
|
||||
*/
|
||||
@ -541,6 +575,39 @@ public interface TargetObjectSchema {
|
||||
}
|
||||
}
|
||||
|
||||
private static class InAggregateSearch extends BreadthFirst<SearchEntry> {
|
||||
final Set<TargetObjectSchema> visited = new HashSet<>();
|
||||
|
||||
public InAggregateSearch(TargetObjectSchema seed) {
|
||||
super(Set.of(new SearchEntry(List.of(), seed)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean descend(SearchEntry ent) {
|
||||
return ent.schema.getInterfaces().contains(TargetAggregate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandAttribute(Set<SearchEntry> nextLevel, SearchEntry ent,
|
||||
TargetObjectSchema schema, List<String> path) {
|
||||
if (visited.add(schema)) {
|
||||
nextLevel.add(new SearchEntry(path, schema));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandDefaultAttribute(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandElements(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandDefaultElement(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchFor(TargetObjectSchema sch, PathMatcher result,
|
||||
List<String> prefix, boolean parentIsCanonical, Class<? extends TargetObject> type,
|
||||
boolean requireCanonical, Set<TargetObjectSchema> visited) {
|
||||
@ -578,40 +645,12 @@ public interface TargetObjectSchema {
|
||||
visited.remove(sch);
|
||||
}
|
||||
|
||||
static List<String> searchForSuitableInAggregate(TargetObjectSchema seed,
|
||||
Class<? extends TargetObject> type) {
|
||||
Set<SearchEntry> init = Set.of(new SearchEntry(List.of(), seed));
|
||||
BreadthFirst<SearchEntry> breadth = new BreadthFirst<>(init) {
|
||||
final Set<TargetObjectSchema> visited = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public boolean descend(SearchEntry ent) {
|
||||
return ent.schema.getInterfaces().contains(TargetAggregate.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandAttribute(Set<SearchEntry> nextLevel, SearchEntry ent,
|
||||
TargetObjectSchema schema, List<String> path) {
|
||||
if (visited.add(schema)) {
|
||||
nextLevel.add(new SearchEntry(path, schema));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandDefaultAttribute(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandElements(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expandDefaultElement(Set<SearchEntry> nextLevel, SearchEntry ent) {
|
||||
}
|
||||
};
|
||||
while (!breadth.allOnLevel.isEmpty()) {
|
||||
Set<SearchEntry> found = breadth.allOnLevel.stream()
|
||||
.filter(ent -> ent.schema.getInterfaces().contains(type))
|
||||
static List<String> searchForInAggregate(TargetObjectSchema seed,
|
||||
Predicate<SearchEntry> predicate) {
|
||||
InAggregateSearch inAgg = new InAggregateSearch(seed);
|
||||
while (!inAgg.allOnLevel.isEmpty()) {
|
||||
Set<SearchEntry> found = inAgg.allOnLevel.stream()
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toSet());
|
||||
if (!found.isEmpty()) {
|
||||
if (found.size() == 1) {
|
||||
@ -619,10 +658,27 @@ public interface TargetObjectSchema {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
breadth.nextLevel();
|
||||
inAgg.nextLevel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<String> searchForSuitableInAggregate(TargetObjectSchema seed,
|
||||
Class<? extends TargetObject> type) {
|
||||
return searchForInAggregate(seed, ent -> ent.schema.getInterfaces().contains(type));
|
||||
}
|
||||
|
||||
static List<String> searchForSuitableContainerInAggregate(TargetObjectSchema seed,
|
||||
Class<? extends TargetObject> type) {
|
||||
return searchForInAggregate(seed, ent -> {
|
||||
if (!ent.schema.isCanonicalContainer()) {
|
||||
return false;
|
||||
}
|
||||
TargetObjectSchema deSchema =
|
||||
ent.schema.getContext().getSchema(ent.schema.getDefaultElementSchema());
|
||||
return deSchema.getInterfaces().contains(type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -704,8 +760,9 @@ public interface TargetObjectSchema {
|
||||
* @return the expected path of the suitable object, or null
|
||||
*/
|
||||
default List<String> searchForSuitable(Class<? extends TargetObject> type, List<String> path) {
|
||||
List<TargetObjectSchema> schemas = getSuccessorSchemas(path);
|
||||
for (; path != null; path = PathUtils.parent(path)) {
|
||||
TargetObjectSchema schema = getSuccessorSchema(path);
|
||||
TargetObjectSchema schema = schemas.get(path.size());
|
||||
if (schema.getInterfaces().contains(type)) {
|
||||
return path;
|
||||
}
|
||||
@ -717,6 +774,30 @@ public interface TargetObjectSchema {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #searchForSuitable(Class, List)}, but searches for the canonical container whose
|
||||
* elements have the given type
|
||||
*
|
||||
* @param type the type of object sought
|
||||
* @param path the path of a seed object
|
||||
* @return the expected path of the suitable container of those objects, or null
|
||||
*/
|
||||
default List<String> searchForSuitableContainer(Class<? extends TargetObject> type,
|
||||
List<String> path) {
|
||||
List<TargetObjectSchema> schemas = getSuccessorSchemas(path);
|
||||
for (; path != null; path = PathUtils.parent(path)) {
|
||||
TargetObjectSchema schema = schemas.get(path.size());
|
||||
if (schema.getInterfaces().contains(type)) {
|
||||
return path;
|
||||
}
|
||||
List<String> inAgg = Private.searchForSuitableContainerInAggregate(schema, type);
|
||||
if (inAgg != null) {
|
||||
return PathUtils.extend(path, inAgg);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest ancestor implementing the given interface along the given path
|
||||
*
|
||||
@ -738,6 +819,32 @@ public interface TargetObjectSchema {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest ancestor which is the canonical container of the given interface
|
||||
*
|
||||
* <p>
|
||||
* If the given path is such a container, it is returned, i.e., it is not strictly an ancestor.
|
||||
*
|
||||
* @param type the interface whose canonical container to search for
|
||||
* @param path the seed path
|
||||
* @return the found path, or {@code null} if no such ancestor was found
|
||||
*/
|
||||
default List<String> searchForAncestorContainer(Class<? extends TargetObject> type,
|
||||
List<String> path) {
|
||||
for (; path != null; path = PathUtils.parent(path)) {
|
||||
TargetObjectSchema schema = getSuccessorSchema(path);
|
||||
if (!schema.isCanonicalContainer()) {
|
||||
continue;
|
||||
}
|
||||
TargetObjectSchema deSchema =
|
||||
schema.getContext().getSchema(schema.getDefaultElementSchema());
|
||||
if (deSchema.getInterfaces().contains(type)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given key should be hidden for an object having this schema
|
||||
*
|
||||
|
@ -54,8 +54,7 @@ import ghidra.trace.database.symbol.*;
|
||||
import ghidra.trace.database.target.DBTraceObjectManager;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.property.TraceAddressPropertyManager;
|
||||
@ -67,8 +66,7 @@ import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
// TODO: Need some subscription model to ensure record lifespans stay within lifespan of threads
|
||||
@ -219,6 +217,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbError(IOException e) {
|
||||
if (e instanceof ClosedException) {
|
||||
throw new TraceClosedException(e);
|
||||
}
|
||||
super.dbError(e);
|
||||
}
|
||||
|
||||
protected void fixedProgramViewRemoved(RemovalNotification<Long, DBTraceProgramView> rn) {
|
||||
Msg.debug(this, "Dropped cached fixed view at snap=" + rn.getKey());
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package ghidra.trace.database.module;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.trace.database.target.DBTraceObject;
|
||||
import ghidra.trace.database.target.DBTraceObjectInterface;
|
||||
@ -109,8 +110,10 @@ public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectIn
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String key = object.getCanonicalPath().key();
|
||||
String index = PathUtils.isIndex(key) ? PathUtils.parseIndex(key) : key;
|
||||
return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
|
||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, "");
|
||||
TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,7 +79,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||
// TODO: Performance test this
|
||||
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
||||
addressSet = temp;
|
||||
System.err.println("Recomputed: " + temp);
|
||||
}
|
||||
|
||||
protected MemoryBlock getRegionBlock(TraceMemoryRegion region) {
|
||||
|
@ -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.trace.model;
|
||||
|
||||
import ghidra.framework.model.DomainObjectException;
|
||||
|
||||
public class TraceClosedException extends DomainObjectException {
|
||||
public TraceClosedException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user