mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-28 09:40:26 +08:00
GP-2555: Refactor StackProvider for object-mode trace.
This commit is contained in:
+1
-1
@@ -120,7 +120,7 @@ public class GdbModelTargetStackFrame
|
|||||||
return impl.gateFuture(frame.setActive(false));
|
return impl.gateFuture(frame.setActive(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME, hidden = true)
|
||||||
public String getFunction() {
|
public String getFunction() {
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -107,7 +107,11 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
|||||||
if (object != null) {
|
if (object != null) {
|
||||||
return iface.cast(recorder.getTargetObject(object));
|
return iface.cast(recorder.getTargetObject(object));
|
||||||
}
|
}
|
||||||
return recorder.getFocus().getCachedSuitable(iface);
|
TargetObject focus = recorder.getFocus();
|
||||||
|
if (focus == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return focus.getCachedSuitable(iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract boolean isEnabledForObject(T t);
|
abstract boolean isEnabledForObject(T t);
|
||||||
|
|||||||
+4
-4
@@ -45,14 +45,14 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void valueCreated(TraceObjectValue value) {
|
protected void valueCreated(TraceObjectValue value) {
|
||||||
if (query != null && query.includes(span, value)) {
|
if (query != null && query.involves(span, value)) {
|
||||||
reload(); // Can I be more surgical?
|
reload(); // Can I be more surgical?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void valueDeleted(TraceObjectValue value) {
|
protected void valueDeleted(TraceObjectValue value) {
|
||||||
if (query != null && query.includes(span, value)) {
|
if (query != null && query.involves(span, value)) {
|
||||||
reload();
|
reload(); // Can I be more surgical?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
|||||||
}
|
}
|
||||||
boolean inOld = span.intersects(oldSpan);
|
boolean inOld = span.intersects(oldSpan);
|
||||||
boolean inNew = span.intersects(newSpan);
|
boolean inNew = span.intersects(newSpan);
|
||||||
boolean queryIncludes = query.includes(Lifespan.ALL, value);
|
boolean queryIncludes = query.involves(Lifespan.ALL, value);
|
||||||
if (queryIncludes) {
|
if (queryIncludes) {
|
||||||
if (inOld != inNew) {
|
if (inOld != inNew) {
|
||||||
reload();
|
reload();
|
||||||
|
|||||||
+26
-3
@@ -25,17 +25,20 @@ import javax.swing.JPanel;
|
|||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableModel<T>>
|
||||||
|
extends JPanel {
|
||||||
|
|
||||||
protected final AbstractQueryTableModel<T> tableModel;
|
protected final M tableModel;
|
||||||
protected final GhidraTable table;
|
protected final GhidraTable table;
|
||||||
protected final GhidraTableFilterPanel<T> filterPanel;
|
protected final GhidraTableFilterPanel<T> filterPanel;
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||||||
add(filterPanel, BorderLayout.SOUTH);
|
add(filterPanel, BorderLayout.SOUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract AbstractQueryTableModel<T> createModel(Plugin plugin);
|
protected abstract M createModel(Plugin plugin);
|
||||||
|
|
||||||
public void goToCoordinates(DebuggerCoordinates coords) {
|
public void goToCoordinates(DebuggerCoordinates coords) {
|
||||||
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
||||||
@@ -184,6 +187,26 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||||||
return filterPanel.getSelectedItem();
|
return filterPanel.getSelectedItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<T> getAllItems() {
|
||||||
|
return List.copyOf(tableModel.getModelData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <V> DynamicTableColumn<T, V, Trace> getColumnByNameAndType(String name, Class<V> type) {
|
||||||
|
int count = tableModel.getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<T, ?, ?> column = tableModel.getColumn(i);
|
||||||
|
if (!name.equals(column.getColumnName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (column.getColumnClass() != type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return (DynamicTableColumn<T, V, Trace>) column;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDiffColor(Color diffColor) {
|
public void setDiffColor(Color diffColor) {
|
||||||
tableModel.setDiffColor(diffColor);
|
tableModel.setDiffColor(diffColor);
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-15
@@ -15,19 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model;
|
package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||||
import ghidra.dbg.util.*;
|
import ghidra.dbg.util.*;
|
||||||
import ghidra.trace.database.DBTraceUtils;
|
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
|
|
||||||
public class ModelQuery {
|
public class ModelQuery {
|
||||||
|
public static final ModelQuery EMPTY = new ModelQuery(PathPredicates.EMPTY);
|
||||||
// TODO: A more capable query language, e.g., with WHERE clauses.
|
// TODO: A more capable query language, e.g., with WHERE clauses.
|
||||||
// Could also want math expressions for the conditionals... Hmm.
|
// Could also want math expressions for the conditionals... Hmm.
|
||||||
// They need to be user enterable, so just a Java API won't suffice.
|
// They need to be user enterable, so just a Java API won't suffice.
|
||||||
@@ -112,6 +113,23 @@ public class ModelQuery {
|
|||||||
return trace.getObjectManager().getValuePaths(span, predicates).map(p -> p);
|
return trace.getObjectManager().getValuePaths(span, predicates).map(p -> p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TargetObjectSchema> computeSchemas(Trace trace) {
|
||||||
|
TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema();
|
||||||
|
return predicates.getPatterns()
|
||||||
|
.stream()
|
||||||
|
.map(p -> rootSchema.getSuccessorSchema(p.asPath()))
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TargetObjectSchema computeSingleSchema(Trace trace) {
|
||||||
|
List<TargetObjectSchema> schemas = computeSchemas(trace);
|
||||||
|
if (schemas.size() != 1) {
|
||||||
|
return EnumerableTargetObjectSchema.OBJECT;
|
||||||
|
}
|
||||||
|
return schemas.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the named attributes for resulting objects, according to the schema
|
* Compute the named attributes for resulting objects, according to the schema
|
||||||
*
|
*
|
||||||
@@ -122,15 +140,31 @@ public class ModelQuery {
|
|||||||
* @return the list of attributes
|
* @return the list of attributes
|
||||||
*/
|
*/
|
||||||
public Stream<AttributeSchema> computeAttributes(Trace trace) {
|
public Stream<AttributeSchema> computeAttributes(Trace trace) {
|
||||||
TraceObjectManager objects = trace.getObjectManager();
|
TargetObjectSchema schema = computeSingleSchema(trace);
|
||||||
TargetObjectSchema schema =
|
|
||||||
objects.getRootSchema().getSuccessorSchema(predicates.getSingletonPattern().asPath());
|
|
||||||
return schema.getAttributeSchemas()
|
return schema.getAttributeSchemas()
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(as -> !"".equals(as.getName()));
|
.filter(as -> !"".equals(as.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static boolean includes(Lifespan span, PathPattern pattern, TraceObjectValue value) {
|
||||||
|
List<String> asPath = pattern.asPath();
|
||||||
|
if (asPath.isEmpty()) {
|
||||||
|
// If the pattern is the root, then only match the "root value"
|
||||||
|
return value.getParent() == null;
|
||||||
|
}
|
||||||
|
if (!PathPredicates.keyMatches(PathUtils.getKey(asPath), value.getEntryKey())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TraceObject parent = value.getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
// Value is the root. We would already have matched above
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return parent.getAncestors(span, pattern.removeRight(1))
|
||||||
|
.anyMatch(v -> v.getSource(parent).isRoot());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether this query would include the given value in its result
|
* Determine whether this query would include the given value in its result
|
||||||
*
|
*
|
||||||
@@ -144,21 +178,57 @@ public class ModelQuery {
|
|||||||
* @return true if the value would be accepted
|
* @return true if the value would be accepted
|
||||||
*/
|
*/
|
||||||
public boolean includes(Lifespan span, TraceObjectValue value) {
|
public boolean includes(Lifespan span, TraceObjectValue value) {
|
||||||
List<String> path = predicates.getSingletonPattern().asPath();
|
|
||||||
if (path.isEmpty()) {
|
|
||||||
return value.getParent() == null;
|
|
||||||
}
|
|
||||||
if (!PathPredicates.keyMatches(PathUtils.getKey(path), value.getEntryKey())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!span.intersects(value.getLifespan())) {
|
if (!span.intersects(value.getLifespan())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
for (PathPattern pattern : predicates.getPatterns()) {
|
||||||
|
if (includes(span, pattern, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean involves(Lifespan span, PathPattern pattern, TraceObjectValue value) {
|
||||||
TraceObject parent = value.getParent();
|
TraceObject parent = value.getParent();
|
||||||
|
// Every query involves the root
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the value's paths could be an ancestor of a result
|
||||||
|
List<String> asPath = new ArrayList<>(pattern.asPath());
|
||||||
|
// Destroy the pattern from the right, thus iterating each ancestor
|
||||||
|
while (!asPath.isEmpty()) {
|
||||||
|
// The value's key much match somewhere in the pattern to be involved
|
||||||
|
if (!PathPredicates.keyMatches(PathUtils.getKey(asPath), value.getEntryKey())) {
|
||||||
|
asPath.remove(asPath.size() - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If it does, then check if any path to the value's parent matches the rest
|
||||||
|
asPath.remove(asPath.size() - 1);
|
||||||
|
if (parent.getAncestors(span, new PathPattern(asPath))
|
||||||
|
.anyMatch(v -> v.getSource(parent).isRoot())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the query results could depend on the given value
|
||||||
|
*
|
||||||
|
* @return true if the query results depend on the given value
|
||||||
|
*/
|
||||||
|
public boolean involves(Lifespan span, TraceObjectValue value) {
|
||||||
|
if (!span.intersects(value.getLifespan())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return parent.getAncestors(span, predicates.removeRight(1))
|
for (PathPattern pattern : predicates.getPatterns()) {
|
||||||
.anyMatch(v -> v.getSource(parent).isRoot());
|
if (involves(span, pattern, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+92
-17
@@ -37,9 +37,6 @@ import ghidra.trace.model.target.TraceObjectValue;
|
|||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
|
|
||||||
public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
/** Initialized in {@link #createTableColumnDescriptor()}, which precedes this. */
|
|
||||||
private TraceValueValColumn valueColumn;
|
|
||||||
private TraceValueLifePlotColumn lifePlotColumn;
|
|
||||||
|
|
||||||
protected static Stream<? extends TraceObjectValue> distinctCanonical(
|
protected static Stream<? extends TraceObjectValue> distinctCanonical(
|
||||||
Stream<? extends TraceObjectValue> stream) {
|
Stream<? extends TraceObjectValue> stream) {
|
||||||
@@ -272,6 +269,19 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class AutoAttributeColumn extends TraceValueObjectAttributeColumn {
|
||||||
|
public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx,
|
||||||
|
AttributeSchema attributeSchema) {
|
||||||
|
String name = attributeSchema.getName();
|
||||||
|
Class<?> type = computeColumnType(ctx, attributeSchema);
|
||||||
|
return new AutoAttributeColumn(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoAttributeColumn(String attributeName, Class<?> attributeType) {
|
||||||
|
super(attributeName, attributeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Save and restore these between sessions, esp., their settings
|
// TODO: Save and restore these between sessions, esp., their settings
|
||||||
private Map<ColKey, TraceValueObjectAttributeColumn> columnCache = new HashMap<>();
|
private Map<ColKey, TraceValueObjectAttributeColumn> columnCache = new HashMap<>();
|
||||||
|
|
||||||
@@ -307,7 +317,13 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
protected void updateTimelineMax() {
|
protected void updateTimelineMax() {
|
||||||
Long max = getTrace() == null ? null : getTrace().getTimeManager().getMaxSnap();
|
Long max = getTrace() == null ? null : getTrace().getTimeManager().getMaxSnap();
|
||||||
Lifespan fullRange = Lifespan.span(0L, max == null ? 1 : max + 1);
|
Lifespan fullRange = Lifespan.span(0L, max == null ? 1 : max + 1);
|
||||||
lifePlotColumn.setFullRange(fullRange);
|
int count = getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
|
if (column instanceof TraceValueLifePlotColumn plotCol) {
|
||||||
|
plotCol.setFullRange(fullRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<AttributeSchema> computeAttributeSchemas() {
|
protected List<AttributeSchema> computeAttributeSchemas() {
|
||||||
@@ -337,7 +353,6 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
else {
|
else {
|
||||||
SchemaContext ctx = trace.getObjectManager().getRootSchema().getContext();
|
SchemaContext ctx = trace.getObjectManager().getRootSchema().getContext();
|
||||||
attributes = query.computeAttributes(trace)
|
attributes = query.computeAttributes(trace)
|
||||||
.filter(a -> isShowHidden() || !a.isHidden())
|
|
||||||
.filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer())
|
.filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -346,6 +361,9 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
|
|
||||||
protected Set<DynamicTableColumn<ValueRow, ?, ?>> computeAttributeColumns(
|
protected Set<DynamicTableColumn<ValueRow, ?, ?>> computeAttributeColumns(
|
||||||
Collection<AttributeSchema> attributes) {
|
Collection<AttributeSchema> attributes) {
|
||||||
|
if (attributes == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
Trace trace = getTrace();
|
Trace trace = getTrace();
|
||||||
if (trace == null) {
|
if (trace == null) {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
@@ -357,25 +375,31 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
SchemaContext ctx = rootSchema.getContext();
|
SchemaContext ctx = rootSchema.getContext();
|
||||||
return attributes.stream()
|
return attributes.stream()
|
||||||
.map(as -> columnCache.computeIfAbsent(ColKey.fromSchema(ctx, as),
|
.map(as -> columnCache.computeIfAbsent(ColKey.fromSchema(ctx, as),
|
||||||
ck -> TraceValueObjectAttributeColumn.fromSchema(ctx, as)))
|
ck -> AutoAttributeColumn.fromSchema(ctx, as)))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resyncAttributeColumns(Collection<AttributeSchema> attributes) {
|
protected void resyncAttributeColumns(Collection<AttributeSchema> attributes) {
|
||||||
Set<DynamicTableColumn<ValueRow, ?, ?>> columns =
|
Map<Boolean, List<AttributeSchema>> byVisible = attributes == null ? Map.of()
|
||||||
new HashSet<>(computeAttributeColumns(attributes));
|
: attributes.stream()
|
||||||
|
.collect(Collectors.groupingBy(a -> !a.isHidden() || isShowHidden()));
|
||||||
|
Set<DynamicTableColumn<ValueRow, ?, ?>> visibleColumns =
|
||||||
|
new HashSet<>(computeAttributeColumns(byVisible.get(true)));
|
||||||
|
Set<DynamicTableColumn<ValueRow, ?, ?>> hiddenColumns =
|
||||||
|
new HashSet<>(computeAttributeColumns(byVisible.get(false)));
|
||||||
Set<DynamicTableColumn<ValueRow, ?, ?>> toRemove = new HashSet<>();
|
Set<DynamicTableColumn<ValueRow, ?, ?>> toRemove = new HashSet<>();
|
||||||
for (int i = 0; i < getColumnCount(); i++) {
|
for (int i = 0; i < getColumnCount(); i++) {
|
||||||
DynamicTableColumn<ValueRow, ?, ?> exists = getColumn(i);
|
DynamicTableColumn<ValueRow, ?, ?> exists = getColumn(i);
|
||||||
if (!(exists instanceof TraceValueObjectAttributeColumn)) {
|
if (!(exists instanceof AutoAttributeColumn)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!columns.remove(exists)) {
|
if (!visibleColumns.remove(exists) && !hiddenColumns.remove(exists)) {
|
||||||
toRemove.add(exists);
|
toRemove.add(exists);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeTableColumns(toRemove);
|
removeTableColumns(toRemove);
|
||||||
addTableColumns(columns);
|
addTableColumns(visibleColumns, true);
|
||||||
|
addTableColumns(hiddenColumns, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -389,9 +413,9 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||||
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||||
descriptor.addVisibleColumn(new TraceValueKeyColumn());
|
descriptor.addVisibleColumn(new TraceValueKeyColumn());
|
||||||
descriptor.addVisibleColumn(valueColumn = new TraceValueValColumn());
|
descriptor.addVisibleColumn(new TraceValueValColumn());
|
||||||
descriptor.addVisibleColumn(new TraceValueLifeColumn());
|
descriptor.addVisibleColumn(new TraceValueLifeColumn());
|
||||||
descriptor.addHiddenColumn(lifePlotColumn = new TraceValueLifePlotColumn());
|
descriptor.addHiddenColumn(new TraceValueLifePlotColumn());
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,9 +429,38 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the row whose object is the canonical ancestor to the given object
|
||||||
|
*
|
||||||
|
* @param successor the given object
|
||||||
|
* @return the row or null
|
||||||
|
*/
|
||||||
|
public ValueRow findTraceObjectAncestor(TraceObject successor) {
|
||||||
|
for (ValueRow row : getModelData()) {
|
||||||
|
TraceObjectValue value = row.getValue();
|
||||||
|
if (!value.isObject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!value.getChild().getCanonicalPath().isAncestor(successor.getCanonicalPath())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDiffColor(Color diffColor) {
|
public void setDiffColor(Color diffColor) {
|
||||||
valueColumn.setDiffColor(diffColor);
|
int count = getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
|
if (column instanceof TraceValueObjectAttributeColumn attrCol) {
|
||||||
|
attrCol.setDiffColor(diffColor);
|
||||||
|
}
|
||||||
|
else if (column instanceof TraceValueValColumn valCol) {
|
||||||
|
valCol.setDiffColor(diffColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
||||||
column.setDiffColor(diffColor);
|
column.setDiffColor(diffColor);
|
||||||
}
|
}
|
||||||
@@ -415,7 +468,16 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDiffColorSel(Color diffColorSel) {
|
public void setDiffColorSel(Color diffColorSel) {
|
||||||
valueColumn.setDiffColorSel(diffColorSel);
|
int count = getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
|
if (column instanceof TraceValueObjectAttributeColumn attrCol) {
|
||||||
|
attrCol.setDiffColorSel(diffColorSel);
|
||||||
|
}
|
||||||
|
else if (column instanceof TraceValueValColumn valCol) {
|
||||||
|
valCol.setDiffColorSel(diffColorSel);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
||||||
column.setDiffColorSel(diffColorSel);
|
column.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
@@ -424,11 +486,24 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
@Override
|
@Override
|
||||||
protected void snapChanged() {
|
protected void snapChanged() {
|
||||||
super.snapChanged();
|
super.snapChanged();
|
||||||
lifePlotColumn.setSnap(getSnap());
|
long snap = getSnap();
|
||||||
|
int count = getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
|
if (column instanceof TraceValueLifePlotColumn plotCol) {
|
||||||
|
plotCol.setSnap(snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSeekListener(SeekListener listener) {
|
public void addSeekListener(SeekListener listener) {
|
||||||
lifePlotColumn.addSeekListener(listener);
|
int count = getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<ValueRow, ?, ?> column = getColumn(i);
|
||||||
|
if (column instanceof TraceValueLifePlotColumn plotCol) {
|
||||||
|
plotCol.addSeekListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-3
@@ -24,6 +24,7 @@ import docking.widgets.tree.GTreeLazyNode;
|
|||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||||
import ghidra.framework.model.DomainObjectClosedListener;
|
import ghidra.framework.model.DomainObjectClosedListener;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
@@ -189,6 +190,19 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
public abstract class AbstractNode extends GTreeLazyNode {
|
public abstract class AbstractNode extends GTreeLazyNode {
|
||||||
public abstract TraceObjectValue getValue();
|
public abstract TraceObjectValue getValue();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(GTreeNode node) {
|
||||||
|
return TargetObjectKeyComparator.CHILD.compare(this.getName(), node.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return getValue().getEntryKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getDisplayText();
|
||||||
|
|
||||||
protected void childCreated(TraceObjectValue value) {
|
protected void childCreated(TraceObjectValue value) {
|
||||||
if (getParent() == null || !isLoaded()) {
|
if (getParent() == null || !isLoaded()) {
|
||||||
return;
|
return;
|
||||||
@@ -249,6 +263,11 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
return "Root";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayText() {
|
||||||
if (trace == null) {
|
if (trace == null) {
|
||||||
return "<html><em>No trace is active</em>";
|
return "<html><em>No trace is active</em>";
|
||||||
}
|
}
|
||||||
@@ -332,7 +351,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getDisplayText() {
|
||||||
String html = HTMLUtilities.escapeHTML(
|
String html = HTMLUtilities.escapeHTML(
|
||||||
value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()));
|
value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()));
|
||||||
return "<html>" + html;
|
return "<html>" + html;
|
||||||
@@ -380,7 +399,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getDisplayText() {
|
||||||
return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey()) + ": <em>" +
|
return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey()) + ": <em>" +
|
||||||
HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value)) + "</em>";
|
HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value)) + "</em>";
|
||||||
}
|
}
|
||||||
@@ -422,7 +441,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getDisplayText() {
|
||||||
return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value));
|
return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +625,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
List<GTreeNode> result = ObjectTableModel
|
List<GTreeNode> result = ObjectTableModel
|
||||||
.distinctCanonical(object.getValues().stream().filter(this::isValueVisible))
|
.distinctCanonical(object.getValues().stream().filter(this::isValueVisible))
|
||||||
.map(v -> nodeCache.getOrCreateNode(v))
|
.map(v -> nodeCache.getOrCreateNode(v))
|
||||||
|
.sorted()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -17,14 +17,24 @@ package 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.ObjectTableModel.ValueRow;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
|
||||||
public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow> {
|
public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow, ObjectTableModel> {
|
||||||
public ObjectsTablePanel(Plugin plugin) {
|
public ObjectsTablePanel(Plugin plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AbstractQueryTableModel<ValueRow> createModel(Plugin plugin) {
|
protected ObjectTableModel createModel(Plugin plugin) {
|
||||||
return new ObjectTableModel(plugin);
|
return new ObjectTableModel(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean trySelectAncestor(TraceObject successor) {
|
||||||
|
ValueRow row = tableModel.findTraceObjectAncestor(successor);
|
||||||
|
if (row == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setSelectedItem(row);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -18,13 +18,13 @@ package ghidra.app.plugin.core.debug.gui.model;
|
|||||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
|
||||||
public class PathsTablePanel extends AbstractQueryTablePanel<PathRow> {
|
public class PathsTablePanel extends AbstractQueryTablePanel<PathRow, PathTableModel> {
|
||||||
public PathsTablePanel(Plugin plugin) {
|
public PathsTablePanel(Plugin plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AbstractQueryTableModel<PathRow> createModel(Plugin plugin) {
|
protected PathTableModel createModel(Plugin plugin) {
|
||||||
return new PathTableModel(plugin);
|
return new PathTableModel(plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -15,8 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
@@ -32,4 +35,9 @@ public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, St
|
|||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
return rowObject.getKey();
|
return rowObject.getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comparator<String> getComparator() {
|
||||||
|
return TargetObjectKeyComparator.CHILD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-8
@@ -105,13 +105,6 @@ public class TraceValueObjectAttributeColumn
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx,
|
|
||||||
AttributeSchema attributeSchema) {
|
|
||||||
String name = attributeSchema.getName();
|
|
||||||
Class<?> type = computeColumnType(ctx, attributeSchema);
|
|
||||||
return new TraceValueObjectAttributeColumn(name, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String attributeName;
|
private final String attributeName;
|
||||||
private final Class<?> attributeType;
|
private final Class<?> attributeType;
|
||||||
private final AttributeRenderer renderer = new AttributeRenderer();
|
private final AttributeRenderer renderer = new AttributeRenderer();
|
||||||
@@ -155,7 +148,7 @@ public class TraceValueObjectAttributeColumn
|
|||||||
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object getAttributeValue(ValueRow row) {
|
public Object getAttributeValue(ValueRow row) {
|
||||||
TraceObjectValue edge = row.getAttribute(attributeName);
|
TraceObjectValue edge = row.getAttribute(attributeName);
|
||||||
return edge == null ? null : edge.getValue();
|
return edge == null ? null : edge.getValue();
|
||||||
}
|
}
|
||||||
|
|||||||
+480
@@ -0,0 +1,480 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.stack;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
|
import docking.widgets.table.CustomToStringCellRenderer;
|
||||||
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
|
||||||
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.dbg.DebugModelConventions;
|
||||||
|
import ghidra.dbg.target.TargetStackFrame;
|
||||||
|
import ghidra.framework.plugintool.AutoService;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.lang.Register;
|
||||||
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
||||||
|
import ghidra.trace.model.Trace.TraceStackChangeType;
|
||||||
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
|
import ghidra.trace.util.TraceRegisterUtils;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.table.GhidraTable;
|
||||||
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
import utilities.util.SuppressableCallback;
|
||||||
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
|
|
||||||
|
protected enum StackTableColumns
|
||||||
|
implements EnumeratedTableColumn<StackTableColumns, StackFrameRow> {
|
||||||
|
LEVEL("Level", Integer.class, StackFrameRow::getFrameLevel),
|
||||||
|
PC("PC", Address.class, StackFrameRow::getProgramCounter),
|
||||||
|
FUNCTION("Function", ghidra.program.model.listing.Function.class, StackFrameRow::getFunction),
|
||||||
|
COMMENT("Comment", String.class, StackFrameRow::getComment, StackFrameRow::setComment, StackFrameRow::isCommentable);
|
||||||
|
|
||||||
|
private final String header;
|
||||||
|
private final Function<StackFrameRow, ?> getter;
|
||||||
|
private final BiConsumer<StackFrameRow, Object> setter;
|
||||||
|
private final Predicate<StackFrameRow> editable;
|
||||||
|
private final Class<?> cls;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
<T> StackTableColumns(String header, Class<T> cls, Function<StackFrameRow, T> getter,
|
||||||
|
BiConsumer<StackFrameRow, T> setter, Predicate<StackFrameRow> editable) {
|
||||||
|
this.header = header;
|
||||||
|
this.cls = cls;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = (BiConsumer<StackFrameRow, Object>) setter;
|
||||||
|
this.editable = editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> StackTableColumns(String header, Class<T> cls, Function<StackFrameRow, T> getter) {
|
||||||
|
this(header, cls, getter, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getValueClass() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueOf(StackFrameRow row) {
|
||||||
|
return getter.apply(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueOf(StackFrameRow row, Object value) {
|
||||||
|
setter.accept(row, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable(StackFrameRow row) {
|
||||||
|
return setter != null && editable.test(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class StackTableModel
|
||||||
|
extends DefaultEnumeratedColumnTableModel<StackTableColumns, StackFrameRow> {
|
||||||
|
|
||||||
|
public StackTableModel(PluginTool tool) {
|
||||||
|
super(tool, "Stack", StackTableColumns.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StackTableColumns> defaultSortOrder() {
|
||||||
|
return List.of(StackTableColumns.LEVEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForStackListener extends TraceDomainObjectListener {
|
||||||
|
public ForStackListener() {
|
||||||
|
listenFor(TraceStackChangeType.ADDED, this::stackAdded);
|
||||||
|
listenFor(TraceStackChangeType.CHANGED, this::stackChanged);
|
||||||
|
listenFor(TraceStackChangeType.DELETED, this::stackDeleted);
|
||||||
|
|
||||||
|
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stackAdded(TraceStack stack) {
|
||||||
|
if (stack.getSnap() != current.getViewSnap()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceThread curThread = current.getThread();
|
||||||
|
if (curThread != stack.getThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stackChanged(TraceStack stack) {
|
||||||
|
if (currentStack != stack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stackDeleted(TraceStack stack) {
|
||||||
|
if (currentStack != stack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For updating a synthetic frame
|
||||||
|
private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) {
|
||||||
|
TraceThread curThread = current.getThread();
|
||||||
|
if (space.getThread() != curThread || space.getFrameLevel() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!current.getView().getViewport().containsAnyUpper(range.getLifespan())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<StackFrameRow> stackData = stackTableModel.getModelData();
|
||||||
|
if (stackData.isEmpty() || !(stackData.get(0) instanceof StackFrameRow.Synthetic)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StackFrameRow.Synthetic frameRow = (StackFrameRow.Synthetic) stackData.get(0);
|
||||||
|
Trace trace = current.getTrace();
|
||||||
|
Register pc = trace.getBaseLanguage().getProgramCounter();
|
||||||
|
if (!TraceRegisterUtils.rangeForRegister(pc).intersects(range.getRange())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceMemorySpace regs =
|
||||||
|
trace.getMemoryManager().getMemoryRegisterSpace(curThread, false);
|
||||||
|
RegisterValue value = regs.getViewValue(current.getViewSnap(), pc);
|
||||||
|
Address address = trace.getBaseLanguage()
|
||||||
|
.getDefaultSpace()
|
||||||
|
.getAddress(value.getUnsignedValue().longValue());
|
||||||
|
frameRow.updateProgramCounter(address);
|
||||||
|
stackTableModel.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForFunctionsListener implements DebuggerStaticMappingChangeListener {
|
||||||
|
@Override
|
||||||
|
public void mappingsChanged(Set<Trace> affectedTraces, Set<Program> affectedPrograms) {
|
||||||
|
Trace curTrace = current.getTrace();
|
||||||
|
if (curTrace == null || !affectedTraces.contains(curTrace)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Swing.runIfSwingOrRunLater(() -> stackTableModel.fireTableDataChanged());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final DebuggerStackProvider provider;
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
// @AutoServiceConsumed by method
|
||||||
|
private DebuggerModelService modelService;
|
||||||
|
// @AutoServiceConsumed via method
|
||||||
|
DebuggerStaticMappingService mappingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerListingService listingService; // TODO: Goto pc on double-click
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private MarkerService markerService; // TODO: Mark non-current frame PCs, too. (separate plugin?)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
// Table rows access this for function name resolution
|
||||||
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
private Trace currentTrace; // Copy for transition
|
||||||
|
|
||||||
|
private TraceStack currentStack;
|
||||||
|
|
||||||
|
private ForStackListener forStackListener = new ForStackListener();
|
||||||
|
private ForFunctionsListener forFunctionsListener = new ForFunctionsListener();
|
||||||
|
|
||||||
|
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
||||||
|
|
||||||
|
protected final StackTableModel stackTableModel;
|
||||||
|
protected GhidraTable stackTable;
|
||||||
|
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
|
||||||
|
|
||||||
|
private DebuggerStackActionContext myActionContext;
|
||||||
|
|
||||||
|
public DebuggerLegacyStackPanel(DebuggerStackPlugin plugin, DebuggerStackProvider provider) {
|
||||||
|
super(new BorderLayout());
|
||||||
|
this.provider = provider;
|
||||||
|
stackTableModel = new StackTableModel(provider.getTool());
|
||||||
|
|
||||||
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
stackTable = new GhidraTable(stackTableModel);
|
||||||
|
add(new JScrollPane(stackTable));
|
||||||
|
stackFilterPanel = new GhidraTableFilterPanel<>(stackTable, stackTableModel);
|
||||||
|
add(stackFilterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
stackTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||||
|
if (evt.getValueIsAdjusting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
contextChanged();
|
||||||
|
activateSelectedFrame();
|
||||||
|
});
|
||||||
|
|
||||||
|
stackTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() < 2 || e.getButton() != MouseEvent.BUTTON1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (listingService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (myActionContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Address pc = myActionContext.getFrame().getProgramCounter();
|
||||||
|
if (pc == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listingService.goTo(pc, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
int selectedRow = stackTable.getSelectedRow();
|
||||||
|
StackFrameRow row = stackTableModel.getRowObject(selectedRow);
|
||||||
|
rowActivated(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Adjust default column widths?
|
||||||
|
TableColumnModel columnModel = stackTable.getColumnModel();
|
||||||
|
|
||||||
|
TableColumn levelCol = columnModel.getColumn(StackTableColumns.LEVEL.ordinal());
|
||||||
|
levelCol.setPreferredWidth(25);
|
||||||
|
TableColumn baseCol = columnModel.getColumn(StackTableColumns.PC.ordinal());
|
||||||
|
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void contextChanged() {
|
||||||
|
StackFrameRow row = stackFilterPanel.getSelectedItem();
|
||||||
|
myActionContext =
|
||||||
|
row == null ? null : new DebuggerStackActionContext(provider, row, stackTable);
|
||||||
|
provider.contextChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void activateSelectedFrame() {
|
||||||
|
// TODO: Action to toggle sync?
|
||||||
|
if (myActionContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (traceManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cbFrameSelected.invoke(() -> {
|
||||||
|
traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowActivated(StackFrameRow row) {
|
||||||
|
if (row == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceStackFrame frame = row.frame;
|
||||||
|
if (frame == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceThread thread = frame.getStack().getThread();
|
||||||
|
Trace trace = thread.getTrace();
|
||||||
|
TraceRecorder recorder = modelService.getRecorder(trace);
|
||||||
|
if (recorder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TargetStackFrame targetFrame = recorder.getTargetStackFrame(thread, frame.getLevel());
|
||||||
|
if (targetFrame == null || !targetFrame.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DebugModelConventions.requestActivation(targetFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateStack() {
|
||||||
|
Set<TraceStackFrame> toAdd = new LinkedHashSet<>(currentStack.getFrames(current.getSnap()));
|
||||||
|
for (Iterator<StackFrameRow> it = stackTableModel.getModelData().iterator(); it
|
||||||
|
.hasNext();) {
|
||||||
|
StackFrameRow row = it.next();
|
||||||
|
if (!toAdd.remove(row.frame)) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
row.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TraceStackFrame frame : toAdd) {
|
||||||
|
stackTableModel.add(new StackFrameRow(this, frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
stackTableModel.fireTableDataChanged();
|
||||||
|
|
||||||
|
selectCurrentFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doSetCurrentStack(TraceStack stack) {
|
||||||
|
if (stack == null) {
|
||||||
|
currentStack = null;
|
||||||
|
stackTableModel.clear();
|
||||||
|
contextChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentStack == stack && stack.hasFixedFrames()) {
|
||||||
|
stackTableModel.fireTableDataChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentStack = stack;
|
||||||
|
stackTableModel.clear();
|
||||||
|
for (TraceStackFrame frame : currentStack.getFrames(current.getSnap())) {
|
||||||
|
stackTableModel.add(new StackFrameRow(this, frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synthesize a stack with only one frame, taking PC from the registers
|
||||||
|
*/
|
||||||
|
protected void doSetSyntheticStack() {
|
||||||
|
stackTableModel.clear();
|
||||||
|
currentStack = null;
|
||||||
|
|
||||||
|
Trace curTrace = current.getTrace();
|
||||||
|
TraceMemorySpace regs =
|
||||||
|
curTrace.getMemoryManager().getMemoryRegisterSpace(current.getThread(), false);
|
||||||
|
if (regs == null) {
|
||||||
|
contextChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Register pc = curTrace.getBaseLanguage().getProgramCounter();
|
||||||
|
if (pc == null) {
|
||||||
|
contextChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RegisterValue value = regs.getViewValue(current.getViewSnap(), pc);
|
||||||
|
if (value == null) {
|
||||||
|
contextChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Address address = curTrace.getBaseLanguage()
|
||||||
|
.getDefaultSpace()
|
||||||
|
.getAddress(value.getUnsignedValue().longValue(), true);
|
||||||
|
stackTableModel.add(new StackFrameRow.Synthetic(this, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadStack() {
|
||||||
|
TraceThread curThread = current.getThread();
|
||||||
|
if (curThread == null) {
|
||||||
|
doSetCurrentStack(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: getLatestViewStack? Conventionally, I don't expect any scratch stacks, yet.
|
||||||
|
TraceStack stack =
|
||||||
|
current.getTrace().getStackManager().getLatestStack(curThread, current.getViewSnap());
|
||||||
|
if (stack == null) {
|
||||||
|
doSetSyntheticStack();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doSetCurrentStack(stack);
|
||||||
|
}
|
||||||
|
selectCurrentFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOldListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.removeListener(forStackListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.addListener(forStackListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSetTrace(Trace trace) {
|
||||||
|
if (currentTrace == trace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeOldListeners();
|
||||||
|
this.currentTrace = trace;
|
||||||
|
addNewListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
current = coordinates;
|
||||||
|
doSetTrace(current.getTrace());
|
||||||
|
loadStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void selectCurrentFrame() {
|
||||||
|
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
||||||
|
StackFrameRow row =
|
||||||
|
stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
|
||||||
|
if (row == null) {
|
||||||
|
// Strange
|
||||||
|
stackTable.clearSelection();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stackFilterPanel.setSelectedItem(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerStackActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
public void setModelService(DebuggerModelService modelService) {
|
||||||
|
this.modelService = modelService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private void setMappingService(DebuggerStaticMappingService mappingService) {
|
||||||
|
if (this.mappingService != null) {
|
||||||
|
this.mappingService.removeChangeListener(forFunctionsListener);
|
||||||
|
}
|
||||||
|
this.mappingService = mappingService;
|
||||||
|
if (this.mappingService != null) {
|
||||||
|
this.mappingService.addChangeListener(forFunctionsListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.stack;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
|
import docking.widgets.table.TableColumnDescriptor;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.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.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.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.*;
|
||||||
|
import utilities.util.SuppressableCallback;
|
||||||
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
public class DebuggerStackPanel extends ObjectsTablePanel implements ListSelectionListener {
|
||||||
|
|
||||||
|
private static class FrameLevelColumn extends TraceValueKeyColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Level";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnPreferredWidth() {
|
||||||
|
return 48;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FramePcColumn extends TraceValueObjectAttributeColumn {
|
||||||
|
public FramePcColumn() {
|
||||||
|
super(TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "PC";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FrameFunctionColumn
|
||||||
|
extends AbstractDynamicTableColumn<ValueRow, Function, Trace> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Function";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
|
TraceObjectValue value = rowObject.getAttribute(TargetStackFrame.PC_ATTRIBUTE_NAME);
|
||||||
|
return value == null ? null : provider.getFunction((Address) value.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StackTableModel extends ObjectTableModel {
|
||||||
|
protected StackTableModel(Plugin plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||||
|
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||||
|
descriptor.addVisibleColumn(new FrameLevelColumn());
|
||||||
|
descriptor.addVisibleColumn(new FramePcColumn());
|
||||||
|
descriptor.addVisibleColumn(new FrameFunctionColumn());
|
||||||
|
// TODO: Comment column?
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DebuggerStackProvider provider;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
setLimitToSnap(true);
|
||||||
|
setShowHidden(false);
|
||||||
|
|
||||||
|
addSelectionListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ObjectTableModel createModel(Plugin plugin) {
|
||||||
|
return new StackTableModel(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
if (stackPath == null) {
|
||||||
|
return ModelQuery.EMPTY;
|
||||||
|
}
|
||||||
|
TargetObjectSchema stackSchema = rootSchema.getSuccessorSchema(stackPath);
|
||||||
|
PathMatcher matcher = stackSchema.searchFor(TargetStackFrame.class, stackPath, true);
|
||||||
|
return new ModelQuery(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
TraceObject object = coordinates.getObject();
|
||||||
|
setQuery(computeQuery(object));
|
||||||
|
goToCoordinates(coordinates);
|
||||||
|
|
||||||
|
if (object != null) {
|
||||||
|
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
||||||
|
trySelectAncestor(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
ValueRow item = getSelectedItem();
|
||||||
|
if (item != null) {
|
||||||
|
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-15
@@ -22,20 +22,24 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
|||||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger stack view", //
|
shortDescription = "Debugger stack view",
|
||||||
description = "GUI to navigate the stack", //
|
description = "GUI to navigate the stack",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
TraceActivatedPluginEvent.class, //
|
TraceActivatedPluginEvent.class,
|
||||||
TraceClosedPluginEvent.class, //
|
TraceClosedPluginEvent.class,
|
||||||
}, //
|
},
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerStackPlugin extends AbstractDebuggerPlugin {
|
public class DebuggerStackPlugin extends AbstractDebuggerPlugin {
|
||||||
|
|
||||||
protected DebuggerStackProvider provider;
|
protected DebuggerStackProvider provider;
|
||||||
@@ -58,9 +62,11 @@ public class DebuggerStackPlugin extends AbstractDebuggerPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
super.processEvent(event);
|
super.processEvent(event);
|
||||||
if (event instanceof TraceActivatedPluginEvent) {
|
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
|
||||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||||
}
|
}
|
||||||
|
else if (event instanceof TraceClosedPluginEvent ev) {
|
||||||
|
provider.traceClosed(ev.getTrace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-425
File diff suppressed because it is too large
Load Diff
+9
-29
@@ -17,18 +17,15 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
|||||||
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.trace.model.*;
|
|
||||||
import ghidra.trace.model.stack.TraceStackFrame;
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
public class StackFrameRow {
|
public class StackFrameRow {
|
||||||
public static class Synthetic extends StackFrameRow {
|
public static class Synthetic extends StackFrameRow {
|
||||||
private Address pc;
|
private Address pc;
|
||||||
|
|
||||||
public Synthetic(DebuggerStackProvider provider, Address pc) {
|
public Synthetic(DebuggerLegacyStackPanel panel, Address pc) {
|
||||||
super(provider);
|
super(panel);
|
||||||
this.pc = pc;
|
this.pc = pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,19 +39,19 @@ public class StackFrameRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DebuggerStackProvider provider;
|
private final DebuggerLegacyStackPanel panel;
|
||||||
|
|
||||||
final TraceStackFrame frame;
|
final TraceStackFrame frame;
|
||||||
private int level;
|
private int level;
|
||||||
|
|
||||||
public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) {
|
public StackFrameRow(DebuggerLegacyStackPanel panel, TraceStackFrame frame) {
|
||||||
this.provider = provider;
|
this.panel = panel;
|
||||||
this.frame = frame;
|
this.frame = frame;
|
||||||
this.level = frame.getLevel();
|
this.level = frame.getLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private StackFrameRow(DebuggerStackProvider provider) {
|
private StackFrameRow(DebuggerLegacyStackPanel panel) {
|
||||||
this.provider = provider;
|
this.panel = panel;
|
||||||
this.frame = null;
|
this.frame = null;
|
||||||
this.level = 0;
|
this.level = 0;
|
||||||
}
|
}
|
||||||
@@ -64,7 +61,7 @@ public class StackFrameRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getSnap() {
|
public long getSnap() {
|
||||||
return provider.current.getSnap();
|
return panel.current.getSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Address getProgramCounter() {
|
public Address getProgramCounter() {
|
||||||
@@ -87,24 +84,7 @@ public class StackFrameRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Function getFunction() {
|
public Function getFunction() {
|
||||||
if (provider.mappingService == null) {
|
return panel.provider.getFunction(getProgramCounter());
|
||||||
return null;
|
|
||||||
}
|
|
||||||
TraceThread curThread = provider.current.getThread();
|
|
||||||
if (curThread == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Address pc = getProgramCounter();
|
|
||||||
if (pc == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
TraceLocation dloc = new DefaultTraceLocation(curThread.getTrace(),
|
|
||||||
curThread, Lifespan.at(getSnap()), pc);
|
|
||||||
ProgramLocation sloc = provider.mappingService.getOpenMappedLocation(dloc);
|
|
||||||
if (sloc == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sloc.getProgram().getFunctionManager().getFunctionContaining(sloc.getAddress());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void update() {
|
protected void update() {
|
||||||
|
|||||||
+27
@@ -57,4 +57,31 @@ public class ModelQueryTest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
assertFalse(threadQuery.includes(Lifespan.before(0), thread0Val));
|
assertFalse(threadQuery.includes(Lifespan.before(0), thread0Val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvolves() throws Throwable {
|
||||||
|
createTrace();
|
||||||
|
|
||||||
|
ModelQuery rootQuery = ModelQuery.parse("");
|
||||||
|
ModelQuery threadQuery = ModelQuery.parse("Processes[].Threads[]");
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
|
||||||
|
TraceObjectValue rootVal =
|
||||||
|
objects.createRootObject(CTX.getSchema(new SchemaName("Session")));
|
||||||
|
|
||||||
|
TraceObjectValue thread0Val =
|
||||||
|
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Threads[0]"))
|
||||||
|
.insert(Lifespan.nowOn(0), ConflictResolution.DENY)
|
||||||
|
.getLastEntry();
|
||||||
|
|
||||||
|
assertTrue(rootQuery.involves(Lifespan.ALL, rootVal));
|
||||||
|
assertFalse(rootQuery.involves(Lifespan.ALL, thread0Val));
|
||||||
|
|
||||||
|
assertTrue(threadQuery.involves(Lifespan.ALL, rootVal));
|
||||||
|
assertTrue(threadQuery.involves(Lifespan.ALL, thread0Val));
|
||||||
|
assertFalse(threadQuery.involves(Lifespan.before(0), thread0Val));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+508
File diff suppressed because it is too large
Load Diff
-124
@@ -1,124 +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.stack;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
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.trace.model.target.TraceObjectKeyPath;
|
|
||||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
import ghidra.util.database.UndoableTransaction;
|
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
|
||||||
|
|
||||||
public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest {
|
|
||||||
|
|
||||||
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' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
|
||||||
<attribute name='Threads' schema='ThreadContainer' />
|
|
||||||
<attribute name='Memory' schema='RegionContainer' />
|
|
||||||
</schema>
|
|
||||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
|
||||||
attributeResync='ONCE'>
|
|
||||||
<element schema='Thread' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
|
||||||
<interface name='Thread' />
|
|
||||||
<interface name='Aggregate' />
|
|
||||||
<attribute name='Stack' schema='Stack' />
|
|
||||||
<attribute name='Registers' schema='RegisterContainer' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
|
||||||
attributeResync='ONCE'>
|
|
||||||
<interface name='Stack' />
|
|
||||||
<element schema='Frame' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
|
|
||||||
<interface name='StackFrame' />
|
|
||||||
</schema>
|
|
||||||
<schema name='RegisterContainer' canonical='yes' elementResync='NEVER'
|
|
||||||
attributeResync='NEVER'>
|
|
||||||
<interface name='RegisterContainer' />
|
|
||||||
<element schema='Register' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Register' elementResync='NEVER' attributeResync='NEVER'>
|
|
||||||
<interface name='Register' />
|
|
||||||
</schema>
|
|
||||||
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
|
|
||||||
attributeResync='ONCE'>
|
|
||||||
<element schema='Region' />
|
|
||||||
</schema>
|
|
||||||
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
|
|
||||||
<interface name='MemoryRegion' />
|
|
||||||
</schema>
|
|
||||||
</context>
|
|
||||||
""");
|
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TraceThread addThread(String n) throws DuplicateNameException {
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
TraceObjectThread thread = (TraceObjectThread) super.addThread(n);
|
|
||||||
TraceObjectKeyPath regsPath = thread.getObject().getCanonicalPath().extend("Registers");
|
|
||||||
tb.trace.getObjectManager()
|
|
||||||
.createObject(regsPath)
|
|
||||||
.insert(thread.getLifespan(), ConflictResolution.DENY);
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+335
-231
File diff suppressed because it is too large
Load Diff
+7
@@ -696,6 +696,13 @@ public interface TargetObjectSchema {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a suitable object with this schema at the given path
|
||||||
|
*
|
||||||
|
* @param type the type of object sought
|
||||||
|
* @param path the path of a seed object
|
||||||
|
* @return the expected path of the suitable object, or null
|
||||||
|
*/
|
||||||
default List<String> searchForSuitable(Class<? extends TargetObject> type, List<String> path) {
|
default List<String> searchForSuitable(Class<? extends TargetObject> type, List<String> path) {
|
||||||
for (; path != null; path = PathUtils.parent(path)) {
|
for (; path != null; path = PathUtils.parent(path)) {
|
||||||
TargetObjectSchema schema = getSuccessorSchema(path);
|
TargetObjectSchema schema = getSuccessorSchema(path);
|
||||||
|
|||||||
+18
-2
@@ -260,7 +260,7 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
* @param column The field to add
|
* @param column The field to add
|
||||||
*/
|
*/
|
||||||
protected void addTableColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column) {
|
protected void addTableColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column) {
|
||||||
addTableColumns(CollectionUtils.asSet(column));
|
addTableColumns(CollectionUtils.asSet(column), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,8 +274,24 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
* @param columns The columns to add
|
* @param columns The columns to add
|
||||||
*/
|
*/
|
||||||
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns) {
|
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns) {
|
||||||
|
addTableColumns(columns, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given columns to the end of the list of columns. This method is intended for
|
||||||
|
* implementations to add custom column objects, rather than relying on generic, discovered
|
||||||
|
* DynamicTableColumn implementations.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>Note: this method assumes that the columns have already been sorted.</b>
|
||||||
|
*
|
||||||
|
* @param columns The columns to add
|
||||||
|
* @param isDefault true if these are default columns
|
||||||
|
*/
|
||||||
|
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns,
|
||||||
|
boolean isDefault) {
|
||||||
for (DynamicTableColumn<ROW_TYPE, ?, ?> column : columns) {
|
for (DynamicTableColumn<ROW_TYPE, ?, ?> column : columns) {
|
||||||
doAddTableColumn(column, getDefaultTableColumns().size(), true);
|
doAddTableColumn(column, getDefaultTableColumns().size(), isDefault);
|
||||||
}
|
}
|
||||||
fireTableStructureChanged();
|
fireTableStructureChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user