GP-2772: Refactor Modules/Sections provider for new trace convention

This commit is contained in:
Dan 2022-11-08 14:12:57 -05:00
parent 5e89b1a886
commit 12f5365d40
34 changed files with 3091 additions and 1119 deletions

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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();

View File

@ -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);
}
};
}
}

View File

@ -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));
}
};
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View File

@ -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() {

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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")));
}
}
}

View File

@ -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));

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

@ -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();

View File

@ -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();

View File

@ -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.
*

View File

@ -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
*

View File

@ -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());
}

View File

@ -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

View File

@ -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) {

View File

@ -0,0 +1,24 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.model;
import ghidra.framework.model.DomainObjectException;
public class TraceClosedException extends DomainObjectException {
public TraceClosedException(Throwable t) {
super(t);
}
}