mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2794: Refactor ThreadsProvider for new trace conventions
This commit is contained in:
parent
c301dd2c89
commit
6c33d872fd
25 changed files with 1989 additions and 923 deletions
|
@ -16,8 +16,7 @@
|
||||||
package ghidra.app.plugin.core.debug;
|
package ghidra.app.plugin.core.debug;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
@ -163,6 +162,7 @@ public class DebuggerCoordinates {
|
||||||
return trace.getThreadManager()
|
return trace.getThreadManager()
|
||||||
.getLiveThreads(snap)
|
.getLiveThreads(snap)
|
||||||
.stream()
|
.stream()
|
||||||
|
.sorted(Comparator.comparing(TraceThread::getKey))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,12 @@ public class DebuggerCoordinates {
|
||||||
return resolveThread(trace, TraceSchedule.ZERO);
|
return resolveThread(trace, TraceSchedule.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TraceObject resolveObject(Trace trace) {
|
private static TraceObject resolveObject(Trace trace, TraceThread thread, Integer frame,
|
||||||
|
TraceSchedule time) {
|
||||||
|
TraceObject object = resolveObject(thread, frame, time);
|
||||||
|
if (object != null) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
return trace.getObjectManager().getRootObject();
|
return trace.getObjectManager().getRootObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +203,7 @@ public class DebuggerCoordinates {
|
||||||
TraceProgramView newView = resolveView(newTrace);
|
TraceProgramView newView = resolveView(newTrace);
|
||||||
TraceSchedule newTime = null; // Allow later resolution
|
TraceSchedule newTime = null; // Allow later resolution
|
||||||
Integer newFrame = resolveFrame(newThread, newTime);
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
TraceObject newObject = resolveObject(newTrace);
|
TraceObject newObject = resolveObject(newTrace, newThread, newFrame, newTime);
|
||||||
return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime,
|
return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime,
|
||||||
newFrame, newObject);
|
newFrame, newObject);
|
||||||
}
|
}
|
||||||
|
@ -242,9 +247,10 @@ public class DebuggerCoordinates {
|
||||||
.getObjectByCanonicalPath(TraceObjectKeyPath.of(object.getPath()));
|
.getObjectByCanonicalPath(TraceObjectKeyPath.of(object.getPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TraceObject resolveObject(TraceRecorder recorder, TraceSchedule time) {
|
private static TraceObject resolveObject(TraceRecorder recorder, TraceThread thread,
|
||||||
|
Integer frame, TraceSchedule time) {
|
||||||
if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) {
|
if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) {
|
||||||
return resolveObject(recorder.getTrace());
|
return resolveObject(recorder.getTrace(), thread, frame, time);
|
||||||
}
|
}
|
||||||
return resolveObject(recorder.getTrace(), recorder.getFocus());
|
return resolveObject(recorder.getTrace(), recorder.getFocus());
|
||||||
}
|
}
|
||||||
|
@ -266,7 +272,7 @@ public class DebuggerCoordinates {
|
||||||
TraceProgramView newView = resolveView(newTrace);
|
TraceProgramView newView = resolveView(newTrace);
|
||||||
TraceSchedule newTime = null; // Allow later resolution
|
TraceSchedule newTime = null; // Allow later resolution
|
||||||
Integer newFrame = resolveFrame(newThread, newTime);
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
TraceObject newObject = resolveObject(newTrace);
|
TraceObject newObject = resolveObject(newTrace, newThread, newFrame, newTime);
|
||||||
return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime,
|
return new DebuggerCoordinates(newTrace, newPlatform, null, newThread, newView, newTime,
|
||||||
newFrame, newObject);
|
newFrame, newObject);
|
||||||
}
|
}
|
||||||
|
@ -294,7 +300,8 @@ public class DebuggerCoordinates {
|
||||||
TraceThread newThread = thread != null ? thread : resolveThread(newRecorder, newTime);
|
TraceThread newThread = thread != null ? thread : resolveThread(newRecorder, newTime);
|
||||||
TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime);
|
TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime);
|
||||||
Integer newFrame = frame != null ? frame : resolveFrame(newRecorder, newThread, newTime);
|
Integer newFrame = frame != null ? frame : resolveFrame(newRecorder, newThread, newTime);
|
||||||
TraceObject newObject = object != null ? object : resolveObject(newRecorder, newTime);
|
TraceObject threadOrFrameObject = resolveObject(newRecorder, newThread, newFrame, newTime);
|
||||||
|
TraceObject newObject = choose(object, threadOrFrameObject);
|
||||||
return new DebuggerCoordinates(newTrace, newPlatform, newRecorder, newThread, newView,
|
return new DebuggerCoordinates(newTrace, newPlatform, newRecorder, newThread, newView,
|
||||||
newTime, newFrame, newObject);
|
newTime, newFrame, newObject);
|
||||||
}
|
}
|
||||||
|
@ -332,13 +339,23 @@ public class DebuggerCoordinates {
|
||||||
*
|
*
|
||||||
* @param ancestor the proposed ancestor
|
* @param ancestor the proposed ancestor
|
||||||
* @param successor the proposed successor
|
* @param successor the proposed successor
|
||||||
* @param time the time to consider (only the snap matters)
|
|
||||||
* @return true if ancestor is in fact an ancestor of successor at the given time
|
* @return true if ancestor is in fact an ancestor of successor at the given time
|
||||||
*/
|
*/
|
||||||
private static boolean isAncestor(TraceObject ancestor, TraceObject successor,
|
private static boolean isAncestor(TraceObject ancestor, TraceObject successor) {
|
||||||
TraceSchedule time) {
|
return ancestor.getCanonicalPath().isAncestor(successor.getCanonicalPath());
|
||||||
return successor.getCanonicalParents(Lifespan.at(time.getSnap()))
|
}
|
||||||
.anyMatch(p -> p == ancestor);
|
|
||||||
|
private static TraceObject choose(TraceObject curObj, TraceObject newObj) {
|
||||||
|
if (curObj == null) {
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
if (newObj == null) {
|
||||||
|
return curObj;
|
||||||
|
}
|
||||||
|
if (isAncestor(newObj, curObj)) {
|
||||||
|
return curObj;
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebuggerCoordinates thread(TraceThread newThread) {
|
public DebuggerCoordinates thread(TraceThread newThread) {
|
||||||
|
@ -358,9 +375,8 @@ public class DebuggerCoordinates {
|
||||||
// Yes, override frame with 0 on thread changes, unless target says otherwise
|
// Yes, override frame with 0 on thread changes, unless target says otherwise
|
||||||
Integer newFrame = resolveFrame(recorder, newThread, newTime);
|
Integer newFrame = resolveFrame(recorder, newThread, newTime);
|
||||||
// Yes, forced frame change may also force object change
|
// Yes, forced frame change may also force object change
|
||||||
TraceObject ancestor = resolveObject(newThread, newFrame, newTime);
|
TraceObject threadOrFrameObject = resolveObject(newThread, newFrame, newTime);
|
||||||
TraceObject newObject =
|
TraceObject newObject = choose(object, threadOrFrameObject);
|
||||||
object != null && isAncestor(ancestor, object, newTime) ? object : ancestor;
|
|
||||||
return new DebuggerCoordinates(newTrace, newPlatform, recorder, newThread, newView, newTime,
|
return new DebuggerCoordinates(newTrace, newPlatform, recorder, newThread, newView, newTime,
|
||||||
newFrame, newObject);
|
newFrame, newObject);
|
||||||
}
|
}
|
||||||
|
@ -384,9 +400,8 @@ public class DebuggerCoordinates {
|
||||||
: resolveThread(trace, recorder, newTime);
|
: resolveThread(trace, recorder, newTime);
|
||||||
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
||||||
Integer newFrame = resolveFrame(newThread, newTime);
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
TraceObject ancestor = resolveObject(newThread, newFrame, newTime);
|
TraceObject threadOrFrameObject = resolveObject(newThread, newFrame, newTime);
|
||||||
TraceObject newObject =
|
TraceObject newObject = choose(object, threadOrFrameObject);
|
||||||
object != null && isAncestor(ancestor, object, newTime) ? object : ancestor;
|
|
||||||
return new DebuggerCoordinates(trace, platform, recorder, newThread, view, newTime,
|
return new DebuggerCoordinates(trace, platform, recorder, newThread, view, newTime,
|
||||||
newFrame, newObject);
|
newFrame, newObject);
|
||||||
}
|
}
|
||||||
|
@ -398,9 +413,8 @@ public class DebuggerCoordinates {
|
||||||
if (Objects.equals(frame, newFrame)) {
|
if (Objects.equals(frame, newFrame)) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
TraceObject ancestor = resolveObject(thread, newFrame, getTime());
|
TraceObject threadOrFrameObject = resolveObject(thread, newFrame, getTime());
|
||||||
TraceObject newObject =
|
TraceObject newObject = choose(object, threadOrFrameObject);
|
||||||
object != null && isAncestor(ancestor, object, getTime()) ? object : ancestor;
|
|
||||||
return new DebuggerCoordinates(trace, platform, recorder, thread, view, time, newFrame,
|
return new DebuggerCoordinates(trace, platform, recorder, thread, view, time, newFrame,
|
||||||
newObject);
|
newObject);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,9 @@ import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Lifespan.*;
|
import ghidra.trace.model.Lifespan.*;
|
||||||
|
@ -75,6 +77,29 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ValueFixedProperty<T> implements ValueProperty<T> {
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public ValueFixedProperty(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getType() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRow getRow() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static abstract class ValueDerivedProperty<T> implements ValueProperty<T> {
|
public static abstract class ValueDerivedProperty<T> implements ValueProperty<T> {
|
||||||
protected final ValueRow row;
|
protected final ValueRow row;
|
||||||
protected final Class<T> type;
|
protected final Class<T> type;
|
||||||
|
@ -353,7 +378,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
protected static class ColKey {
|
protected static class ColKey {
|
||||||
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||||
String name = attributeSchema.getName();
|
String name = attributeSchema.getName();
|
||||||
Class<?> type = TraceValueObjectAttributeColumn.computeColumnType(ctx, attributeSchema);
|
Class<?> type =
|
||||||
|
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
|
||||||
return new ColKey(name, type);
|
return new ColKey(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +421,7 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
public static TraceValueObjectAttributeColumn<?> fromSchema(SchemaContext ctx,
|
public static TraceValueObjectAttributeColumn<?> fromSchema(SchemaContext ctx,
|
||||||
AttributeSchema attributeSchema) {
|
AttributeSchema attributeSchema) {
|
||||||
String name = attributeSchema.getName();
|
String name = attributeSchema.getName();
|
||||||
Class<?> type = computeColumnType(ctx, attributeSchema);
|
Class<?> type = computeAttributeType(ctx, attributeSchema);
|
||||||
return new AutoAttributeColumn<>(name, type);
|
return new AutoAttributeColumn<>(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,4 +654,74 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
initializeSorting();
|
||||||
|
List<ValueRow> modelData = getModelData();
|
||||||
|
|
||||||
|
if (rowIndex < 0 || rowIndex >= modelData.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueRow t = modelData.get(rowIndex);
|
||||||
|
return isColumnEditableForRow(t, columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isColumnEditableForRow(ValueRow t, int columnIndex) {
|
||||||
|
if (columnIndex < 0 || columnIndex >= tableColumns.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace dataSource = getDataSource();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> column =
|
||||||
|
(DynamicTableColumn<ValueRow, ?, Trace>) tableColumns.get(columnIndex);
|
||||||
|
if (!(column instanceof EditableColumn<ValueRow, ?, Trace> editable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return editable.isEditable(t, columnSettings.get(column), dataSource, serviceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||||
|
initializeSorting();
|
||||||
|
List<ValueRow> modelData = getModelData();
|
||||||
|
|
||||||
|
if (rowIndex < 0 || rowIndex >= modelData.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueRow t = modelData.get(rowIndex);
|
||||||
|
setColumnValueForRow(t, aValue, columnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColumnValueForRow(ValueRow t, Object aValue, int columnIndex) {
|
||||||
|
if (columnIndex < 0 || columnIndex >= tableColumns.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace dataSource = getDataSource();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> column =
|
||||||
|
(DynamicTableColumn<ValueRow, ?, Trace>) tableColumns.get(columnIndex);
|
||||||
|
if (!(column instanceof EditableColumn<ValueRow, ?, Trace> editable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Settings settings = columnSettings.get(column);
|
||||||
|
if (!editable.isEditable(t, settings, dataSource, serviceProvider)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doSetValue(editable, t, aValue, settings, dataSource, serviceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <ROW_TYPE, COLUMN_TYPE, DATA_SOURCE> void doSetValue(
|
||||||
|
EditableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOURCE> editable, ROW_TYPE t,
|
||||||
|
Object aValue, Settings settings, DATA_SOURCE dataSource,
|
||||||
|
ServiceProvider serviceProvider) {
|
||||||
|
editable.setValue(t, (COLUMN_TYPE) aValue, settings, dataSource, serviceProvider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,49 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model;
|
package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
|
import docking.widgets.table.GTableTextCellEditor;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
|
||||||
public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow, ObjectTableModel> {
|
public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow, ObjectTableModel> {
|
||||||
|
|
||||||
|
private static class PropertyEditor extends GTableTextCellEditor {
|
||||||
|
private final JTextField textField;
|
||||||
|
|
||||||
|
public PropertyEditor() {
|
||||||
|
super(new JTextField());
|
||||||
|
textField = (JTextField) getComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
|
||||||
|
int row, int column) {
|
||||||
|
super.getTableCellEditorComponent(table, value, isSelected, row, column);
|
||||||
|
if (value instanceof ValueProperty<?> property) {
|
||||||
|
textField.setText(property.getDisplay());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textField.setText(value.toString());
|
||||||
|
}
|
||||||
|
return textField;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCellEditorValue() {
|
||||||
|
Object value = super.getCellEditorValue();
|
||||||
|
return new ValueFixedProperty<>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ObjectsTablePanel(Plugin plugin) {
|
public ObjectsTablePanel(Plugin plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
|
table.setDefaultEditor(ValueProperty.class, new PropertyEditor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* ###
|
||||||
|
* 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 docking.widgets.table.DynamicTableColumn;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
|
||||||
|
public interface EditableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOURCE>
|
||||||
|
extends DynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOURCE> {
|
||||||
|
boolean isEditable(ROW_TYPE row,
|
||||||
|
Settings settings, DATA_SOURCE dataSource, ServiceProvider serviceProvider);
|
||||||
|
|
||||||
|
// TODO: getCellEditor?
|
||||||
|
|
||||||
|
void setValue(ROW_TYPE row, COLUMN_TYPE value, Settings settings, DATA_SOURCE dataSource,
|
||||||
|
ServiceProvider serviceProvider);
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ public class TracePathLastLifespanPlotColumn
|
||||||
|
|
||||||
private final SpanTableCellRenderer<Long> cellRenderer = new SpanTableCellRenderer<>();
|
private final SpanTableCellRenderer<Long> cellRenderer = new SpanTableCellRenderer<>();
|
||||||
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
new RangeCursorTableHeaderRenderer<>();
|
new RangeCursorTableHeaderRenderer<>(0L);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class TraceValueLifePlotColumn
|
||||||
|
|
||||||
private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>();
|
private final SpanSetTableCellRenderer<Long> cellRenderer = new SpanSetTableCellRenderer<>();
|
||||||
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
private final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
new RangeCursorTableHeaderRenderer<>();
|
new RangeCursorTableHeaderRenderer<>(0L);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
|
|
@ -26,12 +26,28 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
|
||||||
public class TraceValueObjectAttributeColumn<T>
|
/**
|
||||||
extends TraceValueObjectPropertyColumn<T> {
|
* A column which displays the object's value for a given attribute
|
||||||
|
*
|
||||||
|
* @param <T> the type of the attribute
|
||||||
|
*/
|
||||||
|
public class TraceValueObjectAttributeColumn<T> extends TraceValueObjectPropertyColumn<T> {
|
||||||
|
|
||||||
public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) {
|
/**
|
||||||
|
* Get the type of a given attribute for the model schema
|
||||||
|
*
|
||||||
|
* @param ctx the schema context
|
||||||
|
* @param attributeSchema the attribute entry from the schema
|
||||||
|
* @return the type, as a Java class
|
||||||
|
*/
|
||||||
|
public static Class<?> computeAttributeType(SchemaContext ctx,
|
||||||
|
AttributeSchema attributeSchema) {
|
||||||
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
|
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
|
||||||
Class<?> type = schema.getType();
|
Class<?> type = schema.getType();
|
||||||
if (type == TargetObject.class) {
|
if (type == TargetObject.class) {
|
||||||
|
@ -57,6 +73,13 @@ public class TraceValueObjectAttributeColumn<T>
|
||||||
|
|
||||||
protected final String attributeName;
|
protected final String attributeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an attribute-value column
|
||||||
|
*
|
||||||
|
* @param attributeName the name of the attribute
|
||||||
|
* @param attributeType the type of the attribute (see
|
||||||
|
* {@link #computeAttributeType(SchemaContext, AttributeSchema)})
|
||||||
|
*/
|
||||||
public TraceValueObjectAttributeColumn(String attributeName, Class<T> attributeType) {
|
public TraceValueObjectAttributeColumn(String attributeName, Class<T> attributeType) {
|
||||||
super(attributeType);
|
super(attributeType);
|
||||||
this.attributeName = attributeName;
|
this.attributeName = attributeName;
|
||||||
|
@ -64,12 +87,6 @@ public class TraceValueObjectAttributeColumn<T>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
/**
|
|
||||||
* TODO: These are going to have "_"-prefixed things.... Sure, they're "hidden", but if we
|
|
||||||
* remove them, we're going to hide important info. I'd like a way in the schema to specify
|
|
||||||
* which "interface attribute" an attribute satisfies. That way, the name can be
|
|
||||||
* human-friendly, but the interface can still find what it needs.
|
|
||||||
*/
|
|
||||||
return attributeName;
|
return attributeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* ###
|
||||||
|
* 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.ValueProperty;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
|
public class TraceValueObjectEditableAttributeColumn<T> extends TraceValueObjectAttributeColumn<T>
|
||||||
|
implements EditableColumn<ValueRow, ValueProperty<T>, Trace> {
|
||||||
|
public TraceValueObjectEditableAttributeColumn(String attributeName, Class<T> attributeType) {
|
||||||
|
super(attributeName, attributeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable(ValueRow row, Settings settings, Trace dataSource,
|
||||||
|
ServiceProvider serviceProvider) {
|
||||||
|
return row != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(ValueRow row, ValueProperty<T> value, Settings settings, Trace dataSource,
|
||||||
|
ServiceProvider serviceProvider) {
|
||||||
|
TraceObject object = row.getValue().getChild();
|
||||||
|
try (UndoableTransaction tid =
|
||||||
|
UndoableTransaction.start(object.getTrace(), "Edit column " + getColumnName())) {
|
||||||
|
object.setAttribute(Lifespan.nowOn(row.currentSnap()), attributeName, value.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
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;
|
||||||
|
import ghidra.trace.model.TraceClosedException;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
|
@ -48,9 +49,18 @@ public class TraceValueValColumn extends AbstractDynamicTableColumn<ValueRow, Va
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
super.getTableCellRendererComponent(data);
|
super.getTableCellRendererComponent(data);
|
||||||
ValueRow row = (ValueRow) data.getValue();
|
ValueRow row = (ValueRow) data.getValue();
|
||||||
setText(row.getHtmlDisplay());
|
try {
|
||||||
setToolTipText(row.getToolTip());
|
setText(row.getHtmlDisplay());
|
||||||
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
setToolTipText(row.getToolTip());
|
||||||
|
setForeground(
|
||||||
|
getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||||
|
}
|
||||||
|
catch (TraceClosedException e) {
|
||||||
|
setText("ERROR: Trace Closed");
|
||||||
|
setToolTipText(
|
||||||
|
"This row is stale, since it refers to a trace that has since been closed");
|
||||||
|
setForeground(getForegroundFor(data.getTable(), false, data.isSelected()));
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,10 +233,9 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
||||||
public DebuggerLegacyStackPanel(DebuggerStackPlugin plugin, DebuggerStackProvider provider) {
|
public DebuggerLegacyStackPanel(DebuggerStackPlugin plugin, DebuggerStackProvider provider) {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
stackTableModel = new StackTableModel(provider.getTool());
|
|
||||||
|
|
||||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
stackTableModel = new StackTableModel(provider.getTool());
|
||||||
stackTable = new GhidraTable(stackTableModel);
|
stackTable = new GhidraTable(stackTableModel);
|
||||||
add(new JScrollPane(stackTable));
|
add(new JScrollPane(stackTable));
|
||||||
stackFilterPanel = new GhidraTableFilterPanel<>(stackTable, stackTableModel);
|
stackFilterPanel = new GhidraTableFilterPanel<>(stackTable, stackTableModel);
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
/* ###
|
||||||
|
* 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.thread;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.widgets.table.*;
|
||||||
|
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
import ghidra.framework.model.DomainObject;
|
||||||
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
|
import ghidra.framework.plugintool.AutoService;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.trace.model.*;
|
||||||
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
|
import ghidra.trace.model.Trace.TraceThreadChangeType;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.util.database.ObjectKey;
|
||||||
|
import ghidra.util.table.GhidraTable;
|
||||||
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
import utilities.util.SuppressableCallback;
|
||||||
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||||
|
|
||||||
|
protected static long orZero(Long l) {
|
||||||
|
return l == null ? 0 : l;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ThreadTableColumns
|
||||||
|
implements EnumeratedTableColumn<ThreadTableColumns, ThreadRow> {
|
||||||
|
NAME("Name", String.class, ThreadRow::getName, ThreadRow::setName, true),
|
||||||
|
CREATED("Created", Long.class, ThreadRow::getCreationSnap, true),
|
||||||
|
DESTROYED("Destroyed", String.class, ThreadRow::getDestructionSnap, true),
|
||||||
|
STATE("State", ThreadState.class, ThreadRow::getState, true),
|
||||||
|
COMMENT("Comment", String.class, ThreadRow::getComment, ThreadRow::setComment, true),
|
||||||
|
PLOT("Plot", Lifespan.class, ThreadRow::getLifespan, false);
|
||||||
|
|
||||||
|
private final String header;
|
||||||
|
private final Function<ThreadRow, ?> getter;
|
||||||
|
private final BiConsumer<ThreadRow, Object> setter;
|
||||||
|
private final boolean sortable;
|
||||||
|
private final Class<?> cls;
|
||||||
|
|
||||||
|
<T> ThreadTableColumns(String header, Class<T> cls, Function<ThreadRow, T> getter,
|
||||||
|
boolean sortable) {
|
||||||
|
this(header, cls, getter, null, sortable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
<T> ThreadTableColumns(String header, Class<T> cls, Function<ThreadRow, T> getter,
|
||||||
|
BiConsumer<ThreadRow, T> setter, boolean sortable) {
|
||||||
|
this.header = header;
|
||||||
|
this.cls = cls;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = (BiConsumer<ThreadRow, Object>) setter;
|
||||||
|
this.sortable = sortable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getValueClass() {
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueOf(ThreadRow row) {
|
||||||
|
return getter.apply(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable(ThreadRow row) {
|
||||||
|
return setter != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSortable() {
|
||||||
|
return sortable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValueOf(ThreadRow row, Object value) {
|
||||||
|
setter.accept(row, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class ThreadTableModel extends RowWrappedEnumeratedColumnTableModel< //
|
||||||
|
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
|
||||||
|
|
||||||
|
public ThreadTableModel(DebuggerThreadsProvider provider) {
|
||||||
|
super(provider.getTool(), "Threads", ThreadTableColumns.class,
|
||||||
|
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ForThreadsListener extends TraceDomainObjectListener {
|
||||||
|
public ForThreadsListener() {
|
||||||
|
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, this::objectRestored);
|
||||||
|
|
||||||
|
listenFor(TraceThreadChangeType.ADDED, this::threadAdded);
|
||||||
|
listenFor(TraceThreadChangeType.CHANGED, this::threadChanged);
|
||||||
|
listenFor(TraceThreadChangeType.LIFESPAN_CHANGED, this::threadChanged);
|
||||||
|
listenFor(TraceThreadChangeType.DELETED, this::threadDeleted);
|
||||||
|
|
||||||
|
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
||||||
|
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void objectRestored(DomainObjectChangeRecord rec) {
|
||||||
|
loadThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void threadAdded(TraceThread thread) {
|
||||||
|
threadTableModel.addItem(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void threadChanged(TraceThread thread) {
|
||||||
|
threadTableModel.updateItem(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void threadDeleted(TraceThread thread) {
|
||||||
|
threadTableModel.deleteItem(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void snapAdded(TraceSnapshot snapshot) {
|
||||||
|
updateTimelineMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void snapDeleted() {
|
||||||
|
updateTimelineMax();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DebuggerThreadsProvider provider;
|
||||||
|
|
||||||
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
private Trace currentTrace; // Copy for transition
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
private final ForThreadsListener forThreadsListener = new ForThreadsListener();
|
||||||
|
|
||||||
|
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
||||||
|
|
||||||
|
/* package access for testing */
|
||||||
|
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
|
||||||
|
final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||||
|
new RangeCursorTableHeaderRenderer<>(0L);
|
||||||
|
|
||||||
|
final ThreadTableModel threadTableModel;
|
||||||
|
final GTable threadTable;
|
||||||
|
final GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
|
||||||
|
|
||||||
|
private ActionContext myActionContext;
|
||||||
|
|
||||||
|
// strong ref
|
||||||
|
SeekListener seekListener;
|
||||||
|
|
||||||
|
public DebuggerLegacyThreadsPanel(DebuggerThreadsPlugin plugin,
|
||||||
|
DebuggerThreadsProvider provider) {
|
||||||
|
super(new BorderLayout());
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
threadTableModel = new ThreadTableModel(provider);
|
||||||
|
threadTable = new GhidraTable(threadTableModel);
|
||||||
|
threadTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
add(new JScrollPane(threadTable));
|
||||||
|
threadFilterPanel = new GhidraTableFilterPanel<>(threadTable, threadTableModel);
|
||||||
|
add(threadFilterPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||||
|
|
||||||
|
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
|
||||||
|
threadTable.addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
setThreadRowActionContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threadTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
setThreadRowActionContext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TableColumnModel columnModel = threadTable.getColumnModel();
|
||||||
|
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
|
||||||
|
colName.setPreferredWidth(100);
|
||||||
|
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
|
||||||
|
colCreated.setPreferredWidth(10);
|
||||||
|
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
|
||||||
|
colDestroyed.setPreferredWidth(10);
|
||||||
|
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
|
||||||
|
colState.setPreferredWidth(20);
|
||||||
|
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
|
||||||
|
colComment.setPreferredWidth(100);
|
||||||
|
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
|
||||||
|
colPlot.setPreferredWidth(200);
|
||||||
|
colPlot.setCellRenderer(spanRenderer);
|
||||||
|
colPlot.setHeaderRenderer(headerRenderer);
|
||||||
|
|
||||||
|
headerRenderer.addSeekListener(seekListener = pos -> {
|
||||||
|
long snap = Math.round(pos);
|
||||||
|
if (current.getTrace() == null || snap < 0) {
|
||||||
|
snap = 0;
|
||||||
|
}
|
||||||
|
traceManager.activateSnap(snap);
|
||||||
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
|
provider.legacyThreadsPanelContextChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOldListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.removeListener(forThreadsListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewListeners() {
|
||||||
|
if (currentTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentTrace.addListener(forThreadsListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSetTrace(Trace trace) {
|
||||||
|
if (currentTrace == trace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeOldListeners();
|
||||||
|
currentTrace = trace;
|
||||||
|
addNewListeners();
|
||||||
|
|
||||||
|
loadThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
current = coordinates;
|
||||||
|
doSetTrace(coordinates.getTrace());
|
||||||
|
doSetThread(coordinates.getThread());
|
||||||
|
doSetSnap(coordinates.getSnap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSetThread(TraceThread thread) {
|
||||||
|
ThreadRow row = threadFilterPanel.getSelectedItem();
|
||||||
|
TraceThread curThread = row == null ? null : row.getThread();
|
||||||
|
if (curThread == thread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||||
|
if (thread != null) {
|
||||||
|
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
threadTable.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSetSnap(long snap) {
|
||||||
|
headerRenderer.setCursorPosition(snap);
|
||||||
|
threadTable.getTableHeader().repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadThreads() {
|
||||||
|
threadTableModel.clear();
|
||||||
|
Trace curTrace = current.getTrace();
|
||||||
|
if (curTrace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceThreadManager manager = curTrace.getThreadManager();
|
||||||
|
threadTableModel.addAllItems(manager.getAllThreads());
|
||||||
|
updateTimelineMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateTimelineMax() {
|
||||||
|
long max = orZero(current.getTrace().getTimeManager().getMaxSnap());
|
||||||
|
Lifespan fullRange = Lifespan.span(0, max + 1);
|
||||||
|
spanRenderer.setFullRange(fullRange);
|
||||||
|
headerRenderer.setFullRange(fullRange);
|
||||||
|
threadTable.getTableHeader().repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void threadRowSelected(ListSelectionEvent e) {
|
||||||
|
if (e.getValueIsAdjusting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ThreadRow row = setThreadRowActionContext();
|
||||||
|
if (row != null && traceManager != null) {
|
||||||
|
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadRow setThreadRowActionContext() {
|
||||||
|
ThreadRow row = threadFilterPanel.getSelectedItem();
|
||||||
|
myActionContext = new DebuggerThreadActionContext(current.getTrace(),
|
||||||
|
row == null ? null : row.getThread());
|
||||||
|
provider.legacyThreadsPanelContextChanged();
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
/* ###
|
||||||
|
* 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.thread;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
|
||||||
|
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||||
|
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.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
import ghidra.dbg.target.*;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
|
import ghidra.docking.settings.Settings;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
|
import utilities.util.SuppressableCallback;
|
||||||
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> {
|
||||||
|
|
||||||
|
protected static ModelQuery successorThreads(TargetObjectSchema rootSchema, List<String> path) {
|
||||||
|
TargetObjectSchema schema = rootSchema.getSuccessorSchema(path);
|
||||||
|
return new ModelQuery(schema.searchFor(TargetThread.class, path, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadPathColumn 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 ThreadNameColumn extends TraceValueValColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class AbstractThreadLifeBoundColumn
|
||||||
|
extends TraceValueObjectPropertyColumn<Long> {
|
||||||
|
public AbstractThreadLifeBoundColumn() {
|
||||||
|
super(Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Long fromLifespan(Lifespan lifespan);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueProperty<Long> getProperty(ValueRow row) {
|
||||||
|
return new ValueDerivedProperty<>(row, Long.class) {
|
||||||
|
@Override
|
||||||
|
public Long getValue() {
|
||||||
|
// De-duplication may not select parent value at current snap
|
||||||
|
TraceObjectValue curVal =
|
||||||
|
row.getValue().getChild().getCanonicalParent(row.currentSnap());
|
||||||
|
if (curVal == null) {
|
||||||
|
// Thread is not actually alive a current snap
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fromLifespan(curVal.getLifespan());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadCreatedColumn extends AbstractThreadLifeBoundColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Created";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Long fromLifespan(Lifespan lifespan) {
|
||||||
|
return lifespan.minIsFinite() ? lifespan.lmin() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadDestroyedColumn extends AbstractThreadLifeBoundColumn {
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Destroyed";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Long fromLifespan(Lifespan lifespan) {
|
||||||
|
return lifespan.maxIsFinite() ? lifespan.lmax() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadStateColumn extends TraceValueObjectAttributeColumn<String> {
|
||||||
|
public ThreadStateColumn() {
|
||||||
|
// NB. The recorder converts enums to strings
|
||||||
|
super(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "State";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadCommentColumn
|
||||||
|
extends TraceValueObjectEditableAttributeColumn<String> {
|
||||||
|
public ThreadCommentColumn() {
|
||||||
|
super(TraceObjectThread.KEY_COMMENT, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return "Comment";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadPlotColumn extends TraceValueLifePlotColumn {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ThreadTableModel extends ObjectTableModel {
|
||||||
|
protected ThreadTableModel(Plugin plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||||
|
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||||
|
descriptor.addHiddenColumn(new ThreadPathColumn());
|
||||||
|
descriptor.addVisibleColumn(new ThreadNameColumn(), 1, true);
|
||||||
|
descriptor.addVisibleColumn(new ThreadCreatedColumn());
|
||||||
|
descriptor.addVisibleColumn(new ThreadDestroyedColumn());
|
||||||
|
descriptor.addVisibleColumn(new ThreadStateColumn());
|
||||||
|
descriptor.addVisibleColumn(new ThreadCommentColumn());
|
||||||
|
descriptor.addVisibleColumn(new ThreadPlotColumn());
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
protected DebuggerTraceManagerService traceManager;
|
||||||
|
|
||||||
|
private final SuppressableCallback<Void> cbThreadSelected = new SuppressableCallback<>();
|
||||||
|
|
||||||
|
private final SeekListener seekListener = pos -> {
|
||||||
|
long snap = Math.round(pos);
|
||||||
|
if (current.getTrace() == null || snap < 0) {
|
||||||
|
snap = 0;
|
||||||
|
}
|
||||||
|
traceManager.activateSnap(snap);
|
||||||
|
};
|
||||||
|
|
||||||
|
public DebuggerThreadsPanel(DebuggerThreadsProvider provider) {
|
||||||
|
super(provider.plugin, provider, TraceObjectThread.class);
|
||||||
|
setLimitToSnap(false); // TODO: Toggle for this?
|
||||||
|
|
||||||
|
tableModel.addTableModelListener(e -> {
|
||||||
|
// This seems a bit heavy handed
|
||||||
|
trySelectCurrentThread();
|
||||||
|
});
|
||||||
|
addSeekListener(seekListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ObjectTableModel createModel(Plugin plugin) {
|
||||||
|
return new ThreadTableModel(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 successorThreads(rootSchema, processPath);
|
||||||
|
}
|
||||||
|
List<String> containerPath =
|
||||||
|
rootSchema.searchForSuitableContainer(TargetThread.class, seedPath);
|
||||||
|
|
||||||
|
if (containerPath != null) {
|
||||||
|
return successorThreads(rootSchema, containerPath);
|
||||||
|
}
|
||||||
|
return successorThreads(rootSchema, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trySelectCurrentThread() {
|
||||||
|
TraceObject object = current.getObject();
|
||||||
|
if (object != null) {
|
||||||
|
try (Suppression supp = cbThreadSelected.suppress(null)) {
|
||||||
|
trySelectAncestor(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
super.coordinatesActivated(coordinates);
|
||||||
|
trySelectCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void valueChanged(ListSelectionEvent e) {
|
||||||
|
super.valueChanged(e);
|
||||||
|
if (e.getValueIsAdjusting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ValueRow item = getSelectedItem();
|
||||||
|
if (item != null) {
|
||||||
|
cbThreadSelected.invoke(() -> {
|
||||||
|
if (current.getTrace() != item.getValue().getTrace()) {
|
||||||
|
// Prevent timing issues during navigation from causing trace changes
|
||||||
|
// Thread table should never cause trace change anyway
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
traceManager.activateObject(item.getValue().getChild());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,20 +23,18 @@ import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger registers manager", //
|
shortDescription = "Debugger registers manager",
|
||||||
description = "GUI to view and modify register values", //
|
description = "GUI to view and modify register values",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = { TraceOpenedPluginEvent.class, //
|
eventsConsumed = { TraceOpenedPluginEvent.class,
|
||||||
TraceClosedPluginEvent.class, //
|
TraceActivatedPluginEvent.class,
|
||||||
TraceActivatedPluginEvent.class, //
|
},
|
||||||
}, //
|
servicesRequired = {
|
||||||
servicesRequired = { //
|
DebuggerTraceManagerService.class,
|
||||||
DebuggerTraceManagerService.class, //
|
})
|
||||||
} //
|
|
||||||
)
|
|
||||||
public class DebuggerThreadsPlugin extends AbstractDebuggerPlugin {
|
public class DebuggerThreadsPlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerThreadsProvider provider;
|
protected DebuggerThreadsProvider provider;
|
||||||
|
|
||||||
|
@ -59,17 +57,9 @@ public class DebuggerThreadsPlugin extends AbstractDebuggerPlugin {
|
||||||
@Override
|
@Override
|
||||||
public void processEvent(PluginEvent event) {
|
public void processEvent(PluginEvent event) {
|
||||||
super.processEvent(event);
|
super.processEvent(event);
|
||||||
if (event instanceof TraceOpenedPluginEvent) {
|
|
||||||
TraceOpenedPluginEvent ev = (TraceOpenedPluginEvent) event;
|
|
||||||
provider.traceOpened(ev.getTrace());
|
|
||||||
}
|
|
||||||
if (event instanceof TraceActivatedPluginEvent) {
|
if (event instanceof TraceActivatedPluginEvent) {
|
||||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||||
}
|
}
|
||||||
if (event instanceof TraceClosedPluginEvent) {
|
|
||||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
|
||||||
provider.traceClosed(ev.getTrace());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,59 +16,37 @@
|
||||||
package ghidra.app.plugin.core.debug.gui.thread;
|
package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Rectangle;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
|
||||||
import javax.swing.table.TableColumn;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.WindowPosition;
|
import docking.WindowPosition;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.widgets.HorizontalTabPanel;
|
|
||||||
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
|
|
||||||
import docking.widgets.dialogs.InputDialog;
|
import docking.widgets.dialogs.InputDialog;
|
||||||
import docking.widgets.table.*;
|
|
||||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||||
import ghidra.dbg.DebugModelConventions;
|
|
||||||
import ghidra.dbg.target.TargetThread;
|
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.plugintool.AutoService;
|
import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
import ghidra.trace.model.Trace.TraceThreadChangeType;
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
|
||||||
import ghidra.util.database.ObjectKey;
|
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
|
||||||
import ghidra.util.table.GhidraTable;
|
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
|
||||||
import utilities.util.SuppressableCallback;
|
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
|
||||||
|
|
||||||
public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
protected static long orZero(Long l) {
|
|
||||||
return l == null ? 0 : l;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -85,134 +63,80 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class ThreadTableModel
|
private class ForSnapsListener extends TraceDomainObjectListener {
|
||||||
extends RowWrappedEnumeratedColumnTableModel< //
|
private Trace currentTrace;
|
||||||
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
|
|
||||||
|
|
||||||
public ThreadTableModel(DebuggerThreadsProvider provider) {
|
public ForSnapsListener() {
|
||||||
super(provider.getTool(), "Threads", ThreadTableColumns.class,
|
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, this::objectRestored);
|
||||||
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ThreadsListener extends TraceDomainObjectListener {
|
|
||||||
public ThreadsListener() {
|
|
||||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
|
||||||
|
|
||||||
listenFor(TraceThreadChangeType.ADDED, this::threadAdded);
|
|
||||||
listenFor(TraceThreadChangeType.CHANGED, this::threadChanged);
|
|
||||||
listenFor(TraceThreadChangeType.LIFESPAN_CHANGED, this::threadChanged);
|
|
||||||
listenFor(TraceThreadChangeType.DELETED, this::threadDeleted);
|
|
||||||
|
|
||||||
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
||||||
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void objectRestored() {
|
private void setTrace(Trace trace) {
|
||||||
loadThreads();
|
if (currentTrace != null) {
|
||||||
|
currentTrace.removeListener(this);
|
||||||
|
}
|
||||||
|
currentTrace = trace;
|
||||||
|
if (currentTrace != null) {
|
||||||
|
currentTrace.addListener(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void threadAdded(TraceThread thread) {
|
private void objectRestored(DomainObjectChangeRecord rec) {
|
||||||
threadTableModel.addItem(thread);
|
contextChanged();
|
||||||
}
|
|
||||||
|
|
||||||
private void threadChanged(TraceThread thread) {
|
|
||||||
threadTableModel.updateItem(thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void threadDeleted(TraceThread thread) {
|
|
||||||
threadTableModel.deleteItem(thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void snapAdded(TraceSnapshot snapshot) {
|
private void snapAdded(TraceSnapshot snapshot) {
|
||||||
updateTimelineMax();
|
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void snapDeleted() {
|
private void snapDeleted() {
|
||||||
updateTimelineMax();
|
contextChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RecordersChangeListener implements CollectionChangeListener<TraceRecorder> {
|
final DebuggerThreadsPlugin plugin;
|
||||||
@Override
|
|
||||||
public void elementAdded(TraceRecorder element) {
|
|
||||||
Swing.runIfSwingOrRunLater(() -> traceTabs.repaint());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void elementModified(TraceRecorder element) {
|
|
||||||
Swing.runIfSwingOrRunLater(() -> traceTabs.repaint());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void elementRemoved(TraceRecorder element) {
|
|
||||||
Swing.runIfSwingOrRunLater(() -> traceTabs.repaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final DebuggerThreadsPlugin plugin;
|
|
||||||
|
|
||||||
// @AutoServiceConsumed by method
|
|
||||||
private DebuggerModelService modelService;
|
|
||||||
// @AutoServiceConsumed by method
|
|
||||||
private DebuggerTraceManagerService traceManager;
|
|
||||||
@AutoServiceConsumed // NB, also by method
|
|
||||||
private DebuggerEmulationService emulationService;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private final AutoService.Wiring autoWiring;
|
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
private Trace currentTrace; // Copy for transition
|
Trace currentTrace; // Copy for transition
|
||||||
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
|
||||||
|
// @AutoServiceConsumed by method
|
||||||
|
DebuggerModelService modelService;
|
||||||
|
// @AutoServiceConsumed by method
|
||||||
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
private final ThreadsListener threadsListener = new ThreadsListener();
|
|
||||||
private final CollectionChangeListener<TraceRecorder> recordersListener =
|
|
||||||
new RecordersChangeListener();
|
|
||||||
private final BooleanChangeAdapter activatePresentChangeListener =
|
private final BooleanChangeAdapter activatePresentChangeListener =
|
||||||
this::changedAutoActivatePresent;
|
this::changedAutoActivatePresent;
|
||||||
private final BooleanChangeAdapter synchronizeFocusChangeListener =
|
private final BooleanChangeAdapter synchronizeFocusChangeListener =
|
||||||
this::changedSynchronizeFocus;
|
this::changedSynchronizeFocus;
|
||||||
/* package access for testing */
|
|
||||||
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
|
|
||||||
final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
|
||||||
new RangeCursorTableHeaderRenderer<>();
|
|
||||||
|
|
||||||
protected final ThreadTableModel threadTableModel = new ThreadTableModel(this);
|
private final ForSnapsListener forSnapsListener = new ForSnapsListener();
|
||||||
|
|
||||||
private JPanel mainPanel;
|
private JPanel mainPanel;
|
||||||
|
|
||||||
HorizontalTabPanel<Trace> traceTabs;
|
DebuggerTraceTabPanel traceTabs;
|
||||||
GTable threadTable;
|
|
||||||
GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
|
|
||||||
JPopupMenu traceTabPopupMenu;
|
JPopupMenu traceTabPopupMenu;
|
||||||
|
DebuggerThreadsPanel panel;
|
||||||
private ActionContext myActionContext;
|
DebuggerLegacyThreadsPanel legacyPanel;
|
||||||
|
|
||||||
DockingAction actionSaveTrace;
|
DockingAction actionSaveTrace;
|
||||||
DockingAction actionStepSnapBackward;
|
|
||||||
DockingAction actionEmulateTickBackward;
|
|
||||||
DockingAction actionEmulateTickForward;
|
|
||||||
DockingAction actionEmulateTickSkipForward;
|
|
||||||
DockingAction actionStepSnapForward;
|
|
||||||
ToggleDockingAction actionSeekTracePresent;
|
ToggleDockingAction actionSeekTracePresent;
|
||||||
ToggleDockingAction actionSyncFocus;
|
ToggleDockingAction actionSyncFocus;
|
||||||
DockingAction actionGoToTime;
|
DockingAction actionGoToTime;
|
||||||
|
|
||||||
DockingAction actionCloseTrace;
|
ActionContext myActionContext;
|
||||||
DockingAction actionCloseOtherTraces;
|
|
||||||
DockingAction actionCloseDeadTraces;
|
|
||||||
DockingAction actionCloseAllTraces;
|
|
||||||
|
|
||||||
// strong refs
|
// strong ref
|
||||||
ToToggleSelectionListener toToggleSelectionListener;
|
ToToggleSelectionListener toToggleSelectionListener;
|
||||||
SeekListener seekListener;
|
|
||||||
|
|
||||||
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) {
|
||||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
|
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_THREADS, plugin.getName());
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
setIcon(DebuggerResources.ICON_PROVIDER_THREADS);
|
setIcon(DebuggerResources.ICON_PROVIDER_THREADS);
|
||||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_THREADS);
|
setHelpLocation(DebuggerResources.HELP_PROVIDER_THREADS);
|
||||||
|
@ -222,24 +146,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
|
||||||
createActions();
|
createActions();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
|
||||||
public void setModelService(DebuggerModelService modelService) {
|
|
||||||
if (this.modelService != null) {
|
|
||||||
this.modelService.removeTraceRecordersChangedListener(recordersListener);
|
|
||||||
}
|
|
||||||
this.modelService = modelService;
|
|
||||||
if (this.modelService != null) {
|
|
||||||
this.modelService.addTraceRecordersChangedListener(recordersListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||||
if (this.traceManager != null) {
|
if (this.traceManager != null) {
|
||||||
|
@ -266,62 +178,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeOldListeners() {
|
private boolean isLegacy(Trace trace) {
|
||||||
if (currentTrace == null) {
|
return trace != null && trace.getObjectManager().getRootSchema() == null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.removeListener(threadsListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.addListener(threadsListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doSetTrace(Trace trace) {
|
|
||||||
if (currentTrace == trace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeOldListeners();
|
|
||||||
currentTrace = trace;
|
|
||||||
addNewListeners();
|
|
||||||
|
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
|
||||||
traceTabs.setSelectedItem(trace);
|
|
||||||
}
|
|
||||||
loadThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doSetThread(TraceThread thread) {
|
|
||||||
ThreadRow row = threadFilterPanel.getSelectedItem();
|
|
||||||
TraceThread curThread = row == null ? null : row.getThread();
|
|
||||||
if (curThread == thread) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
|
||||||
if (thread != null) {
|
|
||||||
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
threadTable.clearSelection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doSetSnap(long snap) {
|
|
||||||
headerRenderer.setCursorPosition(snap);
|
|
||||||
threadTable.getTableHeader().repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void traceOpened(Trace trace) {
|
|
||||||
traceTabs.addItem(trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void traceClosed(Trace trace) {
|
|
||||||
traceTabs.removeItem(trace);
|
|
||||||
// manager will issue activate-null event if current trace is closed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
@ -332,32 +190,30 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
|
||||||
doSetTrace(current.getTrace());
|
traceTabs.coordinatesActivated(coordinates);
|
||||||
doSetThread(current.getThread());
|
if (isLegacy(coordinates.getTrace())) {
|
||||||
doSetSnap(current.getSnap());
|
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||||
|
legacyPanel.coordinatesActivated(coordinates);
|
||||||
setSubTitle(current.getTime().toString());
|
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {
|
||||||
|
mainPanel.remove(panel);
|
||||||
contextChanged();
|
mainPanel.add(legacyPanel);
|
||||||
}
|
mainPanel.validate();
|
||||||
|
}
|
||||||
protected void loadThreads() {
|
}
|
||||||
threadTableModel.clear();
|
else {
|
||||||
Trace curTrace = current.getTrace();
|
legacyPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||||
if (curTrace == null) {
|
panel.coordinatesActivated(coordinates);
|
||||||
return;
|
if (ArrayUtils.indexOf(mainPanel.getComponents(), panel) == -1) {
|
||||||
|
mainPanel.remove(legacyPanel);
|
||||||
|
mainPanel.add(panel);
|
||||||
|
mainPanel.validate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TraceThreadManager manager = curTrace.getThreadManager();
|
|
||||||
threadTableModel.addAllItems(manager.getAllThreads());
|
|
||||||
updateTimelineMax();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateTimelineMax() {
|
forSnapsListener.setTrace(coordinates.getTrace());
|
||||||
long max = orZero(current.getTrace().getTimeManager().getMaxSnap());
|
|
||||||
Lifespan fullRange = Lifespan.span(0, max + 1);
|
setSubTitle(coordinates.getTime().toString());
|
||||||
spanRenderer.setFullRange(fullRange);
|
contextChanged();
|
||||||
headerRenderer.setFullRange(fullRange);
|
|
||||||
threadTable.getTableHeader().repaint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -365,6 +221,14 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
super.addLocalAction(action);
|
super.addLocalAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void legacyThreadsPanelContextChanged() {
|
||||||
|
myActionContext = legacyPanel.getActionContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void traceTabsContextChanged() {
|
||||||
|
myActionContext = traceTabs.getActionContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
if (myActionContext == null) {
|
if (myActionContext == null) {
|
||||||
|
@ -373,131 +237,21 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
return myActionContext;
|
return myActionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rowActivated(ThreadRow row) {
|
|
||||||
if (row == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceThread thread = row.getThread();
|
|
||||||
Trace trace = thread.getTrace();
|
|
||||||
TraceRecorder recorder = modelService.getRecorder(trace);
|
|
||||||
if (recorder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TargetThread targetThread = recorder.getTargetThread(thread);
|
|
||||||
if (targetThread == null || !targetThread.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DebugModelConventions.requestActivation(targetThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void buildMainPanel() {
|
protected void buildMainPanel() {
|
||||||
traceTabPopupMenu = new JPopupMenu("Trace");
|
traceTabPopupMenu = new JPopupMenu("Trace");
|
||||||
|
|
||||||
mainPanel = new JPanel(new BorderLayout());
|
mainPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
threadTable = new GhidraTable(threadTableModel);
|
panel = new DebuggerThreadsPanel(this);
|
||||||
threadTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
legacyPanel = new DebuggerLegacyThreadsPanel(plugin, this);
|
||||||
mainPanel.add(new JScrollPane(threadTable));
|
mainPanel.add(panel);
|
||||||
|
|
||||||
threadFilterPanel = new GhidraTableFilterPanel<>(threadTable, threadTableModel);
|
traceTabs = new DebuggerTraceTabPanel(this);
|
||||||
mainPanel.add(threadFilterPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
|
|
||||||
threadTable.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
setThreadRowActionContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(MouseEvent e) {
|
|
||||||
int selectedRow = threadTable.getSelectedRow();
|
|
||||||
ThreadRow row = threadTableModel.getRowObject(selectedRow);
|
|
||||||
rowActivated(row);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
traceTabs = new HorizontalTabPanel<>();
|
|
||||||
traceTabs.getList().setCellRenderer(new TabListCellRenderer<>() {
|
|
||||||
protected String getText(Trace value) {
|
|
||||||
return value.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Icon getIcon(Trace value) {
|
|
||||||
if (modelService == null) {
|
|
||||||
return super.getIcon(value);
|
|
||||||
}
|
|
||||||
TraceRecorder recorder = modelService.getRecorder(value);
|
|
||||||
if (recorder == null || !recorder.isRecording()) {
|
|
||||||
return super.getIcon(value);
|
|
||||||
}
|
|
||||||
return DebuggerResources.ICON_RECORD;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
JList<Trace> list = traceTabs.getList();
|
|
||||||
list.getSelectionModel().addListSelectionListener(this::traceTabSelected);
|
|
||||||
list.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
setTraceTabActionContext(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(MouseEvent e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mainPanel.add(traceTabs, BorderLayout.NORTH);
|
mainPanel.add(traceTabs, BorderLayout.NORTH);
|
||||||
|
|
||||||
TableColumnModel columnModel = threadTable.getColumnModel();
|
|
||||||
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
|
|
||||||
colName.setPreferredWidth(100);
|
|
||||||
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
|
|
||||||
colCreated.setPreferredWidth(10);
|
|
||||||
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
|
|
||||||
colDestroyed.setPreferredWidth(10);
|
|
||||||
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
|
|
||||||
colState.setPreferredWidth(20);
|
|
||||||
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
|
|
||||||
colComment.setPreferredWidth(100);
|
|
||||||
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
|
|
||||||
colPlot.setPreferredWidth(200);
|
|
||||||
colPlot.setCellRenderer(spanRenderer);
|
|
||||||
colPlot.setHeaderRenderer(headerRenderer);
|
|
||||||
|
|
||||||
headerRenderer.addSeekListener(seekListener = pos -> {
|
|
||||||
long snap = Math.round(pos);
|
|
||||||
if (current.getTrace() == null || snap < 0) {
|
|
||||||
snap = 0;
|
|
||||||
}
|
|
||||||
traceManager.activateSnap(snap);
|
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
|
||||||
contextChanged();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
actionStepSnapBackward = StepSnapBackwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isStepSnapBackwardEnabled)
|
|
||||||
.enabled(false)
|
|
||||||
.onAction(this::activatedStepSnapBackward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionEmulateTickBackward = EmulateTickBackwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isEmulateTickBackwardEnabled)
|
|
||||||
.onAction(this::activatedEmulateTickBackward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionEmulateTickForward = EmulateTickForwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isEmulateTickForwardEnabled)
|
|
||||||
.onAction(this::activatedEmulateTickForward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionEmulateTickSkipForward = EmulateSkipTickForwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isEmulateSkipTickForwardEnabled)
|
|
||||||
.onAction(this::activatedEmulateSkipTickForward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionStepSnapForward = StepSnapForwardAction.builder(plugin)
|
|
||||||
.enabledWhen(this::isStepSnapForwardEnabled)
|
|
||||||
.enabled(false)
|
|
||||||
.onAction(this::activatedStepSnapForward)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionSeekTracePresent = SeekTracePresentAction.builder(plugin)
|
actionSeekTracePresent = SeekTracePresentAction.builder(plugin)
|
||||||
.enabledWhen(this::isSeekTracePresentEnabled)
|
.enabledWhen(this::isSeekTracePresentEnabled)
|
||||||
.onAction(this::toggledSeekTracePresent)
|
.onAction(this::toggledSeekTracePresent)
|
||||||
|
@ -515,125 +269,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
||||||
new ToToggleSelectionListener(actionSyncFocus));
|
new ToToggleSelectionListener(actionSyncFocus));
|
||||||
|
|
||||||
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
|
||||||
.popupWhen(c -> c.getTrace() != null)
|
|
||||||
.onAction(c -> traceManager.closeTrace(c.getTrace()))
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
|
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
|
||||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
|
|
||||||
.onAction(c -> traceManager.closeAllTraces())
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
|
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
|
||||||
.popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
|
|
||||||
.onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
|
|
||||||
.withContext(DebuggerTraceFileActionContext.class)
|
|
||||||
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && modelService != null)
|
|
||||||
.onAction(c -> traceManager.closeDeadTraces())
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStepSnapBackwardEnabled(ActionContext context) {
|
|
||||||
if (current.getTrace() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!current.getTime().isSnapOnly()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (current.getSnap() <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedStepSnapBackward(ActionContext context) {
|
|
||||||
if (current.getTime().isSnapOnly()) {
|
|
||||||
traceManager.activateSnap(current.getSnap() - 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
traceManager.activateSnap(current.getSnap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEmulateTickBackwardEnabled(ActionContext context) {
|
|
||||||
if (emulationService == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (current.getTrace() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (current.getTime().steppedBackward(current.getTrace(), 1) == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedEmulateTickBackward(ActionContext context) {
|
|
||||||
if (current.getTrace() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceSchedule time = current.getTime().steppedBackward(current.getTrace(), 1);
|
|
||||||
if (time == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
traceManager.activateTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEmulateTickForwardEnabled(ActionContext context) {
|
|
||||||
if (emulationService == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (current.getThread() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedEmulateTickForward(ActionContext context) {
|
|
||||||
if (current.getThread() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceSchedule time = current.getTime().steppedForward(current.getThread(), 1);
|
|
||||||
traceManager.activateTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEmulateSkipTickForwardEnabled(ActionContext context) {
|
|
||||||
if (emulationService == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (current.getThread() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedEmulateSkipTickForward(ActionContext context) {
|
|
||||||
if (current.getThread() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceSchedule time = current.getTime().skippedForward(current.getThread(), 1);
|
|
||||||
traceManager.activateTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStepSnapForwardEnabled(ActionContext context) {
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Long maxSnap = curTrace.getTimeManager().getMaxSnap();
|
|
||||||
if (maxSnap == null || current.getSnap() >= maxSnap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedStepSnapForward(ActionContext contetxt) {
|
|
||||||
traceManager.activateSnap(current.getSnap() + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSeekTracePresentEnabled(ActionContext context) {
|
private boolean isSeekTracePresentEnabled(ActionContext context) {
|
||||||
|
@ -684,55 +319,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trace computeClickedTraceTab(MouseEvent e) {
|
|
||||||
JList<Trace> list = traceTabs.getList();
|
|
||||||
int i = list.locationToIndex(e.getPoint());
|
|
||||||
if (i < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Rectangle cell = list.getCellBounds(i, i);
|
|
||||||
if (!cell.contains(e.getPoint())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return traceTabs.getItem(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Trace setTraceTabActionContext(MouseEvent e) {
|
|
||||||
Trace newTrace = e == null ? traceTabs.getSelectedItem() : computeClickedTraceTab(e);
|
|
||||||
actionCloseTrace.getPopupMenuData()
|
|
||||||
.setMenuItemName(
|
|
||||||
CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName()));
|
|
||||||
myActionContext = new DebuggerTraceFileActionContext(newTrace);
|
|
||||||
contextChanged();
|
|
||||||
return newTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void traceTabSelected(ListSelectionEvent e) {
|
|
||||||
if (e.getValueIsAdjusting()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Trace newTrace = setTraceTabActionContext(null);
|
|
||||||
cbCoordinateActivation.invoke(() -> traceManager.activateTrace(newTrace));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ThreadRow setThreadRowActionContext() {
|
|
||||||
ThreadRow row = threadFilterPanel.getSelectedItem();
|
|
||||||
myActionContext = new DebuggerThreadActionContext(current.getTrace(),
|
|
||||||
row == null ? null : row.getThread());
|
|
||||||
contextChanged();
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void threadRowSelected(ListSelectionEvent e) {
|
|
||||||
if (e.getValueIsAdjusting()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ThreadRow row = setThreadRowActionContext();
|
|
||||||
if (row != null && traceManager != null) {
|
|
||||||
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JComponent getComponent() {
|
public JComponent getComponent() {
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
/* ###
|
||||||
|
* 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.thread;
|
||||||
|
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.event.ListSelectionEvent;
|
||||||
|
|
||||||
|
import docking.action.DockingAction;
|
||||||
|
import docking.widgets.HorizontalTabPanel;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
|
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||||
|
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.framework.plugintool.*;
|
||||||
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
|
import ghidra.framework.plugintool.util.PluginEventListener;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
|
import utilities.util.SuppressableCallback;
|
||||||
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
public class DebuggerTraceTabPanel extends HorizontalTabPanel<Trace>
|
||||||
|
implements PluginEventListener {
|
||||||
|
|
||||||
|
private class RecordersChangeListener implements CollectionChangeListener<TraceRecorder> {
|
||||||
|
@Override
|
||||||
|
public void elementAdded(TraceRecorder element) {
|
||||||
|
Swing.runIfSwingOrRunLater(() -> repaint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void elementModified(TraceRecorder element) {
|
||||||
|
Swing.runIfSwingOrRunLater(() -> repaint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void elementRemoved(TraceRecorder element) {
|
||||||
|
Swing.runIfSwingOrRunLater(() -> repaint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DebuggerThreadsPlugin plugin;
|
||||||
|
private final DebuggerThreadsProvider provider;
|
||||||
|
|
||||||
|
// @AutoServiceConsumed by method
|
||||||
|
DebuggerModelService modelService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private DebuggerTraceManagerService traceManager;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
private final CollectionChangeListener<TraceRecorder> recordersListener =
|
||||||
|
new RecordersChangeListener();
|
||||||
|
|
||||||
|
DockingAction actionCloseTrace;
|
||||||
|
DockingAction actionCloseOtherTraces;
|
||||||
|
DockingAction actionCloseDeadTraces;
|
||||||
|
DockingAction actionCloseAllTraces;
|
||||||
|
|
||||||
|
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
||||||
|
|
||||||
|
private DebuggerTraceFileActionContext myActionContext;
|
||||||
|
|
||||||
|
public DebuggerTraceTabPanel(DebuggerThreadsProvider provider) {
|
||||||
|
this.plugin = provider.plugin;
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
|
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
|
||||||
|
PluginTool tool = plugin.getTool();
|
||||||
|
tool.addEventListener(TraceOpenedPluginEvent.class, this);
|
||||||
|
tool.addEventListener(TraceClosedPluginEvent.class, this);
|
||||||
|
|
||||||
|
list.setCellRenderer(new TabListCellRenderer<>() {
|
||||||
|
protected String getText(Trace value) {
|
||||||
|
return value.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Icon getIcon(Trace value) {
|
||||||
|
if (modelService == null) {
|
||||||
|
return super.getIcon(value);
|
||||||
|
}
|
||||||
|
TraceRecorder recorder = modelService.getRecorder(value);
|
||||||
|
if (recorder == null || !recorder.isRecording()) {
|
||||||
|
return super.getIcon(value);
|
||||||
|
}
|
||||||
|
return DebuggerResources.ICON_RECORD;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
list.getSelectionModel().addListSelectionListener(this::traceTabSelected);
|
||||||
|
list.addFocusListener(new FocusAdapter() {
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e) {
|
||||||
|
setTraceTabActionContext(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
list.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
setTraceTabActionContext(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
actionCloseTrace = CloseTraceAction.builderPopup(plugin)
|
||||||
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
|
.popupWhen(c -> c.getTrace() != null)
|
||||||
|
.onAction(c -> traceManager.closeTrace(c.getTrace()))
|
||||||
|
.buildAndInstallLocal(provider);
|
||||||
|
actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin)
|
||||||
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
|
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty())
|
||||||
|
.onAction(c -> traceManager.closeAllTraces())
|
||||||
|
.buildAndInstallLocal(provider);
|
||||||
|
actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin)
|
||||||
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
|
.popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null)
|
||||||
|
.onAction(c -> traceManager.closeOtherTraces(c.getTrace()))
|
||||||
|
.buildAndInstallLocal(provider);
|
||||||
|
actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin)
|
||||||
|
.withContext(DebuggerTraceFileActionContext.class)
|
||||||
|
.popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && modelService != null)
|
||||||
|
.onAction(c -> traceManager.closeDeadTraces())
|
||||||
|
.buildAndInstallLocal(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Trace computeClickedTraceTab(MouseEvent e) {
|
||||||
|
JList<Trace> list = getList();
|
||||||
|
int i = list.locationToIndex(e.getPoint());
|
||||||
|
if (i < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Rectangle cell = list.getCellBounds(i, i);
|
||||||
|
if (!cell.contains(e.getPoint())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getItem(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Trace setTraceTabActionContext(MouseEvent e) {
|
||||||
|
Trace newTrace = e == null ? getSelectedItem() : computeClickedTraceTab(e);
|
||||||
|
actionCloseTrace.getPopupMenuData()
|
||||||
|
.setMenuItemName(
|
||||||
|
CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName()));
|
||||||
|
myActionContext = new DebuggerTraceFileActionContext(newTrace);
|
||||||
|
provider.traceTabsContextChanged();
|
||||||
|
return newTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerTraceFileActionContext getActionContext() {
|
||||||
|
return myActionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||||
|
setSelectedItem(coordinates.getTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoServiceConsumed
|
||||||
|
public void setModelService(DebuggerModelService modelService) {
|
||||||
|
if (this.modelService != null) {
|
||||||
|
this.modelService.removeTraceRecordersChangedListener(recordersListener);
|
||||||
|
}
|
||||||
|
this.modelService = modelService;
|
||||||
|
if (this.modelService != null) {
|
||||||
|
this.modelService.addTraceRecordersChangedListener(recordersListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventSent(PluginEvent event) {
|
||||||
|
if (Objects.equals(event.getSourceName(), plugin.getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event instanceof TraceOpenedPluginEvent evt) {
|
||||||
|
addItem(evt.getTrace());
|
||||||
|
}
|
||||||
|
else if (event instanceof TraceClosedPluginEvent evt) {
|
||||||
|
removeItem(evt.getTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void traceTabSelected(ListSelectionEvent e) {
|
||||||
|
if (e.getValueIsAdjusting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Trace newTrace = setTraceTabActionContext(null);
|
||||||
|
cbCoordinateActivation.invoke(() -> traceManager.activateTrace(newTrace));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ public enum ThreadState {
|
||||||
/**
|
/**
|
||||||
* The last recorded state is alive, but the recorder is not tracking the live thread
|
* The last recorded state is alive, but the recorder is not tracking the live thread
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* This state is generally erroneous. If it is seen, the recorder has fallen out of sync with
|
* This state is generally erroneous. If it is seen, the recorder has fallen out of sync with
|
||||||
* the live session and/or the trace.
|
* the live session and/or the trace.
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +27,6 @@ public enum ThreadState {
|
||||||
/**
|
/**
|
||||||
* The last recorded state is alive, but there is no live session to know STOPPED or RUNNING
|
* The last recorded state is alive, but there is no live session to know STOPPED or RUNNING
|
||||||
*/
|
*/
|
||||||
// TODO: Should the thread state transitions be recorded?
|
|
||||||
ALIVE,
|
ALIVE,
|
||||||
/**
|
/**
|
||||||
* The thread is alive, but suspended
|
* The thread is alive, but suspended
|
||||||
|
|
|
@ -1,82 +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.thread;
|
|
||||||
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
|
||||||
import ghidra.trace.model.Lifespan;
|
|
||||||
|
|
||||||
public enum ThreadTableColumns implements EnumeratedTableColumn<ThreadTableColumns, ThreadRow> {
|
|
||||||
NAME("Name", String.class, ThreadRow::getName, ThreadRow::setName, true),
|
|
||||||
CREATED("Created", Long.class, ThreadRow::getCreationSnap, true),
|
|
||||||
DESTROYED("Destroyed", String.class, ThreadRow::getDestructionSnap, true),
|
|
||||||
STATE("State", ThreadState.class, ThreadRow::getState, true),
|
|
||||||
COMMENT("Comment", String.class, ThreadRow::getComment, ThreadRow::setComment, true),
|
|
||||||
PLOT("Plot", Lifespan.class, ThreadRow::getLifespan, false);
|
|
||||||
|
|
||||||
private final String header;
|
|
||||||
private final Function<ThreadRow, ?> getter;
|
|
||||||
private final BiConsumer<ThreadRow, Object> setter;
|
|
||||||
private final boolean sortable;
|
|
||||||
private final Class<?> cls;
|
|
||||||
|
|
||||||
<T> ThreadTableColumns(String header, Class<T> cls, Function<ThreadRow, T> getter,
|
|
||||||
boolean sortable) {
|
|
||||||
this(header, cls, getter, null, sortable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
<T> ThreadTableColumns(String header, Class<T> cls, Function<ThreadRow, T> getter,
|
|
||||||
BiConsumer<ThreadRow, T> setter, boolean sortable) {
|
|
||||||
this.header = header;
|
|
||||||
this.cls = cls;
|
|
||||||
this.getter = getter;
|
|
||||||
this.setter = (BiConsumer<ThreadRow, Object>) setter;
|
|
||||||
this.sortable = sortable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getValueClass() {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueOf(ThreadRow row) {
|
|
||||||
return getter.apply(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEditable(ThreadRow row) {
|
|
||||||
return setter != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSortable() {
|
|
||||||
return sortable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValueOf(ThreadRow row, Object value) {
|
|
||||||
setter.accept(row, value);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,10 +15,18 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.model;
|
package ghidra.app.plugin.core.debug.gui.model;
|
||||||
|
|
||||||
|
import docking.widgets.table.*;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPanel;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class QueryPanelTestHelper {
|
public class QueryPanelTestHelper {
|
||||||
|
|
||||||
|
public static ObjectTableModel getTableModel(DebuggerThreadsPanel panel) {
|
||||||
|
return panel.tableModel;
|
||||||
|
}
|
||||||
|
|
||||||
public static GhidraTable getTable(AbstractQueryTablePanel<?, ?> panel) {
|
public static GhidraTable getTable(AbstractQueryTablePanel<?, ?> panel) {
|
||||||
return panel.table;
|
return panel.table;
|
||||||
}
|
}
|
||||||
|
@ -27,4 +35,32 @@ public class QueryPanelTestHelper {
|
||||||
AbstractQueryTablePanel<T, ?> panel) {
|
AbstractQueryTablePanel<T, ?> panel) {
|
||||||
return panel.filterPanel;
|
return panel.filterPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static SpannedRenderer<Long> getSpannedCellRenderer(
|
||||||
|
AbstractQueryTablePanel<?, ?> panel) {
|
||||||
|
int count = panel.tableModel.getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<?, ?, ?> column = panel.tableModel.getColumn(i);
|
||||||
|
GColumnRenderer<?> renderer = column.getColumnRenderer();
|
||||||
|
if (renderer instanceof SpannedRenderer<?> spanned) {
|
||||||
|
return (SpannedRenderer<Long>) spanned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static RangeCursorTableHeaderRenderer<Long> getCursorHeaderRenderer(
|
||||||
|
AbstractQueryTablePanel<?, ?> panel) {
|
||||||
|
int count = panel.tableModel.getColumnCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DynamicTableColumn<?, ?, ?> column = panel.tableModel.getColumn(i);
|
||||||
|
GTableHeaderRenderer renderer = column.getHeaderRenderer();
|
||||||
|
if (renderer instanceof RangeCursorTableHeaderRenderer<?> spanned) {
|
||||||
|
return (RangeCursorTableHeaderRenderer<Long>) spanned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,452 @@
|
||||||
|
/* ###
|
||||||
|
* 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.thread;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
import generic.test.category.NightlyCategory;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
|
||||||
|
import ghidra.app.services.TraceRecorder;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
|
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
|
||||||
|
public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
||||||
|
protected DebuggerThreadsPlugin threadsPlugin;
|
||||||
|
protected DebuggerThreadsProvider threadsProvider;
|
||||||
|
|
||||||
|
protected TraceThread thread1;
|
||||||
|
protected TraceThread thread2;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpThreadsProviderTest() throws Exception {
|
||||||
|
threadsPlugin = addPlugin(tool, DebuggerThreadsPlugin.class);
|
||||||
|
threadsProvider = waitForComponentProvider(DebuggerThreadsProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addThreads() throws Exception {
|
||||||
|
TraceThreadManager manager = tb.trace.getThreadManager();
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
thread1 = manager.addThread("Processes[1].Threads[1]", Lifespan.nowOn(0));
|
||||||
|
thread1.setComment("A comment");
|
||||||
|
thread2 = manager.addThread("Processes[1].Threads[2]", Lifespan.span(5, 10));
|
||||||
|
thread2.setComment("Another comment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that there exist no tabs, and that the tab row is invisible
|
||||||
|
*/
|
||||||
|
protected void assertZeroTabs() {
|
||||||
|
assertEquals(0, threadsProvider.traceTabs.getList().getModel().getSize());
|
||||||
|
assertEquals("Tab row should not be visible", 0,
|
||||||
|
threadsProvider.traceTabs.getVisibleRect().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that exactly one tab exists, and that the tab row is visible
|
||||||
|
*/
|
||||||
|
protected void assertOneTabPopulated() {
|
||||||
|
assertEquals(1, threadsProvider.traceTabs.getList().getModel().getSize());
|
||||||
|
assertNotEquals("Tab row should be visible", 0,
|
||||||
|
threadsProvider.traceTabs.getVisibleRect().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertNoTabSelected() {
|
||||||
|
assertTabSelected(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertTabSelected(Trace trace) {
|
||||||
|
assertEquals(trace, threadsProvider.traceTabs.getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThreadsEmpty() {
|
||||||
|
List<ThreadRow> threadsDisplayed =
|
||||||
|
threadsProvider.legacyPanel.threadTableModel.getModelData();
|
||||||
|
assertTrue(threadsDisplayed.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThreadsPopulated() {
|
||||||
|
List<ThreadRow> threadsDisplayed =
|
||||||
|
threadsProvider.legacyPanel.threadTableModel.getModelData();
|
||||||
|
assertEquals(2, threadsDisplayed.size());
|
||||||
|
|
||||||
|
ThreadRow thread1Record = threadsDisplayed.get(0);
|
||||||
|
assertEquals(thread1, thread1Record.getThread());
|
||||||
|
assertEquals("Processes[1].Threads[1]", thread1Record.getName());
|
||||||
|
assertEquals(Lifespan.nowOn(0), thread1Record.getLifespan());
|
||||||
|
assertEquals(0, thread1Record.getCreationSnap());
|
||||||
|
assertEquals("", thread1Record.getDestructionSnap());
|
||||||
|
assertEquals(tb.trace, thread1Record.getTrace());
|
||||||
|
assertEquals(ThreadState.ALIVE, thread1Record.getState());
|
||||||
|
assertEquals("A comment", thread1Record.getComment());
|
||||||
|
|
||||||
|
ThreadRow thread2Record = threadsDisplayed.get(1);
|
||||||
|
assertEquals(thread2, thread2Record.getThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertNoThreadSelected() {
|
||||||
|
assertNull(threadsProvider.legacyPanel.threadFilterPanel.getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThreadSelected(TraceThread thread) {
|
||||||
|
ThreadRow row = threadsProvider.legacyPanel.threadFilterPanel.getSelectedItem();
|
||||||
|
assertNotNull(row);
|
||||||
|
assertEquals(thread, row.getThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertProviderEmpty() {
|
||||||
|
assertZeroTabs();
|
||||||
|
assertThreadsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmpty() {
|
||||||
|
waitForSwing();
|
||||||
|
assertProviderEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenTracePopupatesTab() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertOneTabPopulated();
|
||||||
|
assertNoTabSelected();
|
||||||
|
assertThreadsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateTraceSelectsTab() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertOneTabPopulated();
|
||||||
|
assertTabSelected(tb.trace);
|
||||||
|
|
||||||
|
traceManager.activateTrace(null);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertOneTabPopulated();
|
||||||
|
assertNoTabSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectTabActivatesTrace() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
waitForSwing();
|
||||||
|
threadsProvider.traceTabs.setSelectedItem(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||||
|
assertEquals(tb.trace, threadsProvider.current.getTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateNoTraceEmptiesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated(); // Sanity
|
||||||
|
|
||||||
|
traceManager.activateTrace(null);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertOneTabPopulated();
|
||||||
|
assertTabSelected(tb.trace);
|
||||||
|
|
||||||
|
traceManager.closeTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertZeroTabs();
|
||||||
|
assertNoTabSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCurrentTraceClosedEmptiesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
|
||||||
|
traceManager.closeTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertOneTabPopulated(); // pre-check
|
||||||
|
clickListItem(threadsProvider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
||||||
|
waitForSwing();
|
||||||
|
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
||||||
|
assertMenu(expected, expected);
|
||||||
|
|
||||||
|
clickSubMenuItemByText("Close " + tb.trace.getName());
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
addThreads();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddThreadsThenActivatePopulatesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddSnapUpdatesTimelineMax() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
TraceTimeManager manager = tb.trace.getTimeManager();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(1, threadsProvider.legacyPanel.spanRenderer.getFullRange().max().longValue());
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
manager.getSnapshot(10, true);
|
||||||
|
}
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(11, threadsProvider.legacyPanel.spanRenderer.getFullRange().max().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Do not test delete updates timeline max, as maxSnap does not reflect deletion
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChangeThreadUpdatesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
thread1.setDestructionSnap(15);
|
||||||
|
}
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals("15", threadsProvider.legacyPanel.threadTableModel.getModelData()
|
||||||
|
.get(0)
|
||||||
|
.getDestructionSnap());
|
||||||
|
// NOTE: Plot max is based on time table, never thread destruction
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteThreadUpdatesProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(2, threadsProvider.legacyPanel.threadTableModel.getModelData().size());
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
thread2.delete();
|
||||||
|
}
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(1, threadsProvider.legacyPanel.threadTableModel.getModelData().size());
|
||||||
|
// NOTE: Plot max is based on time table, never thread destruction
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditThreadFields() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
runSwing(() -> {
|
||||||
|
threadsProvider.legacyPanel.threadTableModel.setValueAt("My Thread", 0,
|
||||||
|
ThreadTableColumns.NAME.ordinal());
|
||||||
|
threadsProvider.legacyPanel.threadTableModel.setValueAt("A different comment", 0,
|
||||||
|
ThreadTableColumns.COMMENT.ordinal());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("My Thread", thread1.getName());
|
||||||
|
assertEquals("A different comment", thread1.getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
|
||||||
|
undo(tb.trace);
|
||||||
|
assertThreadsEmpty();
|
||||||
|
|
||||||
|
redo(tb.trace);
|
||||||
|
assertThreadsPopulated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateThreadSelectsThread() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
assertThreadSelected(thread1);
|
||||||
|
|
||||||
|
traceManager.activateThread(thread2);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadSelected(thread2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelectThreadInTableActivatesThread() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
assertThreadSelected(thread1); // Manager selects default if not live
|
||||||
|
|
||||||
|
clickTableCellWithButton(threadsProvider.legacyPanel.threadTable, 1, 0, MouseEvent.BUTTON1);
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertThreadSelected(thread2);
|
||||||
|
assertEquals(thread2, traceManager.getCurrentThread());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateSnapUpdatesTimelineCursor() throws Exception {
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertThreadsPopulated();
|
||||||
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
assertEquals(0, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
|
||||||
|
|
||||||
|
traceManager.activateSnap(6);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionSeekTracePresent() throws Exception {
|
||||||
|
assertTrue(threadsProvider.actionSeekTracePresent.isSelected());
|
||||||
|
|
||||||
|
createAndOpenTrace();
|
||||||
|
addThreads();
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
tb.trace.getTimeManager().createSnapshot("Next snapshot");
|
||||||
|
}
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
// Not live, so no seek
|
||||||
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
tb.close();
|
||||||
|
|
||||||
|
createTestModel();
|
||||||
|
mb.createTestProcessesAndThreads();
|
||||||
|
// Threads needs registers to be recognized by the recorder
|
||||||
|
mb.createTestThreadRegisterBanks();
|
||||||
|
|
||||||
|
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
|
||||||
|
createTargetTraceMapper(mb.testProcess1));
|
||||||
|
Trace trace = recorder.getTrace();
|
||||||
|
|
||||||
|
// Wait till two threads are observed in the database
|
||||||
|
waitForPass(() -> assertEquals(2, trace.getThreadManager().getAllThreads().size()));
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
TraceSnapshot snapshot = recorder.forceSnapshot();
|
||||||
|
waitForDomainObject(trace);
|
||||||
|
|
||||||
|
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
performAction(threadsProvider.actionSeekTracePresent);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertFalse(threadsProvider.actionSeekTracePresent.isSelected());
|
||||||
|
|
||||||
|
recorder.forceSnapshot();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,83 +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.thread;
|
|
||||||
|
|
||||||
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 DebuggerThreadsProviderObjectTest extends DebuggerThreadsProviderTest {
|
|
||||||
|
|
||||||
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='Threads' schema='ThreadContainer' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
|
||||||
" attributeResync='ONCE'>" + //
|
|
||||||
" <element schema='Thread' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
|
||||||
" <interface name='Thread' />" + //
|
|
||||||
" </schema>" + //
|
|
||||||
"</context>");
|
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,46 +18,146 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.List;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.*;
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
import docking.widgets.table.*;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
|
||||||
|
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||||
|
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.TraceRecorder;
|
||||||
|
import ghidra.dbg.target.TargetExecutionStateful;
|
||||||
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||||
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
|
import ghidra.dbg.util.PathPattern;
|
||||||
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.program.model.lang.CompilerSpecID;
|
||||||
|
import ghidra.program.model.lang.LanguageID;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||||
|
import ghidra.trace.model.target.TraceObjectManager;
|
||||||
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import ghidra.util.table.GhidraTable;
|
||||||
|
|
||||||
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
|
@Category(NightlyCategory.class)
|
||||||
public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
|
|
||||||
protected DebuggerThreadsPlugin threadsPlugin;
|
DebuggerThreadsProvider provider;
|
||||||
protected DebuggerThreadsProvider threadsProvider;
|
|
||||||
|
|
||||||
protected TraceThread thread1;
|
protected TraceObjectThread thread1;
|
||||||
protected TraceThread thread2;
|
protected TraceObjectThread thread2;
|
||||||
|
|
||||||
@Before
|
protected SchemaContext ctx;
|
||||||
public void setUpThreadsProviderTest() throws Exception {
|
|
||||||
threadsPlugin = addPlugin(tool, DebuggerThreadsPlugin.class);
|
@Override
|
||||||
threadsProvider = waitForComponentProvider(DebuggerThreadsProvider.class);
|
protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target)
|
||||||
|
throws Exception {
|
||||||
|
return new ObjectBasedDebuggerTargetTraceMapper(target,
|
||||||
|
new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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='Threads' schema='ThreadContainer' />" + //
|
||||||
|
" </schema>" + //
|
||||||
|
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
||||||
|
" attributeResync='ONCE'>" + //
|
||||||
|
" <element schema='Thread' />" + //
|
||||||
|
" </schema>" + //
|
||||||
|
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||||
|
" <interface name='Thread' />" + //
|
||||||
|
" </schema>" + //
|
||||||
|
"</context>");
|
||||||
|
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObjectThread addThread(int index, Lifespan lifespan, String comment) {
|
||||||
|
TraceObjectManager om = tb.trace.getObjectManager();
|
||||||
|
PathPattern threadPattern = new PathPattern(PathUtils.parse("Processes[1].Threads[]"));
|
||||||
|
TraceObjectThread thread = Objects.requireNonNull(om.createObject(
|
||||||
|
TraceObjectKeyPath.of(threadPattern.applyIntKeys(index).getSingletonPath()))
|
||||||
|
.insert(lifespan, ConflictResolution.TRUNCATE)
|
||||||
|
.getDestination(null)
|
||||||
|
.queryInterface(TraceObjectThread.class));
|
||||||
|
thread.getObject()
|
||||||
|
.setAttribute(lifespan, TargetExecutionStateful.STATE_ATTRIBUTE_NAME,
|
||||||
|
TargetExecutionState.STOPPED.name());
|
||||||
|
thread.getObject().setAttribute(lifespan, TraceObjectThread.KEY_COMMENT, comment);
|
||||||
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addThreads() throws Exception {
|
protected void addThreads() throws Exception {
|
||||||
TraceThreadManager manager = tb.trace.getThreadManager();
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
thread1 = manager.addThread("Processes[1].Threads[1]", Lifespan.nowOn(0));
|
thread1 = addThread(1, Lifespan.nowOn(0), "A comment");
|
||||||
thread1.setComment("A comment");
|
thread2 = addThread(2, Lifespan.span(0, 10), "Another comment");
|
||||||
thread2 = manager.addThread("Processes[1].Threads[2]", Lifespan.span(5, 10));
|
|
||||||
thread2.setComment("Another comment");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,18 +165,18 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
* Check that there exist no tabs, and that the tab row is invisible
|
* Check that there exist no tabs, and that the tab row is invisible
|
||||||
*/
|
*/
|
||||||
protected void assertZeroTabs() {
|
protected void assertZeroTabs() {
|
||||||
assertEquals(0, threadsProvider.traceTabs.getList().getModel().getSize());
|
assertEquals(0, provider.traceTabs.getList().getModel().getSize());
|
||||||
assertEquals("Tab row should not be visible", 0,
|
assertEquals("Tab row should not be visible", 0,
|
||||||
threadsProvider.traceTabs.getVisibleRect().height);
|
provider.traceTabs.getVisibleRect().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that exactly one tab exists, and that the tab row is visible
|
* Check that exactly one tab exists, and that the tab row is visible
|
||||||
*/
|
*/
|
||||||
protected void assertOneTabPopulated() {
|
protected void assertOneTabPopulated() {
|
||||||
assertEquals(1, threadsProvider.traceTabs.getList().getModel().getSize());
|
assertEquals(1, provider.traceTabs.getList().getModel().getSize());
|
||||||
assertNotEquals("Tab row should be visible", 0,
|
assertNotEquals("Tab row should be visible", 0,
|
||||||
threadsProvider.traceTabs.getVisibleRect().height);
|
provider.traceTabs.getVisibleRect().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNoTabSelected() {
|
protected void assertNoTabSelected() {
|
||||||
|
@ -84,40 +184,57 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertTabSelected(Trace trace) {
|
protected void assertTabSelected(Trace trace) {
|
||||||
assertEquals(trace, threadsProvider.traceTabs.getSelectedItem());
|
assertEquals(trace, provider.traceTabs.getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertThreadsTableSize(int size) {
|
||||||
|
assertEquals(size, provider.panel.getAllItems().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertThreadsEmpty() {
|
protected void assertThreadsEmpty() {
|
||||||
List<ThreadRow> threadsDisplayed = threadsProvider.threadTableModel.getModelData();
|
assertThreadsTableSize(0);
|
||||||
assertTrue(threadsDisplayed.isEmpty());
|
}
|
||||||
|
|
||||||
|
protected void assertThreadRow(int position, Object object, String name, Long created,
|
||||||
|
Long destroyed, TargetExecutionState state, String comment) {
|
||||||
|
// NB. Not testing plot, since that's unmodified from generic ObjectTable
|
||||||
|
ValueRow row = provider.panel.getAllItems().get(position);
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> nameCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Name", ValueRow.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> createdCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Created", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> destroyedCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Destroyed", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> stateCol =
|
||||||
|
provider.panel.getColumnByNameAndType("State", ValueProperty.class).getValue();
|
||||||
|
DynamicTableColumn<ValueRow, ?, Trace> commentCol =
|
||||||
|
provider.panel.getColumnByNameAndType("Comment", ValueProperty.class).getValue();
|
||||||
|
|
||||||
|
assertSame(object, row.getValue().getValue());
|
||||||
|
assertEquals(name, rowColDisplay(row, nameCol));
|
||||||
|
assertEquals(created, rowColVal(row, createdCol));
|
||||||
|
assertEquals(destroyed, rowColVal(row, destroyedCol));
|
||||||
|
assertEquals(state.name(), rowColVal(row, stateCol));
|
||||||
|
assertEquals(comment, rowColVal(row, commentCol));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertThreadsPopulated() {
|
protected void assertThreadsPopulated() {
|
||||||
List<ThreadRow> threadsDisplayed = threadsProvider.threadTableModel.getModelData();
|
assertThreadsTableSize(2);
|
||||||
assertEquals(2, threadsDisplayed.size());
|
|
||||||
|
|
||||||
ThreadRow thread1Record = threadsDisplayed.get(0);
|
assertThreadRow(0, thread1.getObject(), "Processes[1].Threads[1]", 0L, null,
|
||||||
assertEquals(thread1, thread1Record.getThread());
|
TargetExecutionState.STOPPED, "A comment");
|
||||||
assertEquals("Processes[1].Threads[1]", thread1Record.getName());
|
assertThreadRow(1, thread2.getObject(), "Processes[1].Threads[2]", 0L, 10L,
|
||||||
assertEquals(Lifespan.nowOn(0), thread1Record.getLifespan());
|
TargetExecutionState.STOPPED, "Another comment");
|
||||||
assertEquals(0, thread1Record.getCreationSnap());
|
|
||||||
assertEquals("", thread1Record.getDestructionSnap());
|
|
||||||
assertEquals(tb.trace, thread1Record.getTrace());
|
|
||||||
assertEquals(ThreadState.ALIVE, thread1Record.getState());
|
|
||||||
assertEquals("A comment", thread1Record.getComment());
|
|
||||||
|
|
||||||
ThreadRow thread2Record = threadsDisplayed.get(1);
|
|
||||||
assertEquals(thread2, thread2Record.getThread());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNoThreadSelected() {
|
protected void assertNoThreadSelected() {
|
||||||
assertNull(threadsProvider.threadFilterPanel.getSelectedItem());
|
assertNull(provider.panel.getSelectedItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertThreadSelected(TraceThread thread) {
|
protected void assertThreadSelected(TraceObjectThread thread) {
|
||||||
ThreadRow row = threadsProvider.threadFilterPanel.getSelectedItem();
|
ValueRow row = provider.panel.getSelectedItem();
|
||||||
assertNotNull(row);
|
assertNotNull(row);
|
||||||
assertEquals(thread, row.getThread());
|
assertEquals(thread.getObject(), row.getValue().getChild());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderEmpty() {
|
protected void assertProviderEmpty() {
|
||||||
|
@ -125,47 +242,68 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertThreadsEmpty();
|
assertThreadsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpThreadsProviderTest() throws Exception {
|
||||||
|
addPlugin(tool, DebuggerThreadsPlugin.class);
|
||||||
|
provider = waitForComponentProvider(DebuggerThreadsProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDownThreadsProviderTest() throws Exception {
|
||||||
|
traceManager.activate(DebuggerCoordinates.NOWHERE);
|
||||||
|
waitForTasks();
|
||||||
|
runSwing(() -> traceManager.closeAllTraces());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() {
|
public void testEmpty() {
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
assertProviderEmpty();
|
waitForPass(() -> assertProviderEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOpenTracePopupatesTab() throws Exception {
|
public void testOpenTracePopupatesTab() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertOneTabPopulated();
|
waitForPass(() -> {
|
||||||
assertNoTabSelected();
|
assertOneTabPopulated();
|
||||||
assertThreadsEmpty();
|
assertNoTabSelected();
|
||||||
|
assertThreadsEmpty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActivateTraceSelectsTab() throws Exception {
|
public void testActivateTraceSelectsTab() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertOneTabPopulated();
|
waitForPass(() -> {
|
||||||
assertTabSelected(tb.trace);
|
assertOneTabPopulated();
|
||||||
|
assertTabSelected(tb.trace);
|
||||||
|
});
|
||||||
|
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertOneTabPopulated();
|
waitForPass(() -> {
|
||||||
assertNoTabSelected();
|
assertOneTabPopulated();
|
||||||
|
assertNoTabSelected();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSelectTabActivatesTrace() throws Exception {
|
public void testSelectTabActivatesTrace() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
threadsProvider.traceTabs.setSelectedItem(tb.trace);
|
provider.traceTabs.setSelectedItem(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
waitForPass(() -> {
|
||||||
assertEquals(tb.trace, threadsProvider.current.getTrace());
|
assertEquals(tb.trace, traceManager.getCurrentTrace());
|
||||||
|
assertEquals(tb.trace, provider.current.getTrace());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -173,30 +311,34 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated(); // Sanity
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
|
|
||||||
traceManager.activateTrace(null);
|
traceManager.activateTrace(null);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsEmpty();
|
waitForPass(() -> assertThreadsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
public void testCurrentTraceClosedUpdatesTabs() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertOneTabPopulated();
|
waitForPass(() -> {
|
||||||
assertTabSelected(tb.trace);
|
assertOneTabPopulated();
|
||||||
|
assertTabSelected(tb.trace);
|
||||||
|
});
|
||||||
|
|
||||||
traceManager.closeTrace(tb.trace);
|
traceManager.closeTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertZeroTabs();
|
waitForPass(() -> {
|
||||||
assertNoTabSelected();
|
assertZeroTabs();
|
||||||
|
assertNoTabSelected();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -204,29 +346,29 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
|
|
||||||
traceManager.closeTrace(tb.trace);
|
traceManager.closeTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsEmpty();
|
waitForPass(() -> assertThreadsEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
public void testCloseTraceTabPopupMenuItem() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertOneTabPopulated(); // pre-check
|
waitForPass(() -> assertOneTabPopulated());
|
||||||
clickListItem(threadsProvider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
clickListItem(provider.traceTabs.getList(), 0, MouseEvent.BUTTON3);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
Set<String> expected = Set.of("Close " + tb.trace.getName());
|
||||||
assertMenu(expected, expected);
|
assertMenu(expected, expected);
|
||||||
|
|
||||||
clickSubMenuItemByText("Close " + tb.trace.getName());
|
clickSubMenuItemByText("Close " + tb.trace.getName());
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||||
|
@ -237,24 +379,24 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
public void testActivateThenAddThreadsPopulatesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
addThreads();
|
addThreads();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddThreadsThenActivatePopulatesProvider() throws Exception {
|
public void testAddThreadsThenActivatePopulatesProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -262,16 +404,24 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
TraceTimeManager manager = tb.trace.getTimeManager();
|
TraceTimeManager manager = tb.trace.getTimeManager();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(1, threadsProvider.spanRenderer.getFullRange().max().longValue());
|
waitForPass(() -> {
|
||||||
|
SpannedRenderer<Long> renderer =
|
||||||
|
QueryPanelTestHelper.getSpannedCellRenderer(provider.panel);
|
||||||
|
assertEquals(1, renderer.getFullRange().max().longValue());
|
||||||
|
});
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
manager.getSnapshot(10, true);
|
manager.getSnapshot(10, true);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(11, threadsProvider.spanRenderer.getFullRange().max().longValue());
|
waitForPass(() -> {
|
||||||
|
SpannedRenderer<Long> renderer =
|
||||||
|
QueryPanelTestHelper.getSpannedCellRenderer(provider.panel);
|
||||||
|
assertEquals(11, renderer.getFullRange().max().longValue());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Do not test delete updates timeline max, as maxSnap does not reflect deletion
|
// NOTE: Do not test delete updates timeline max, as maxSnap does not reflect deletion
|
||||||
|
@ -281,16 +431,18 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
thread1.setDestructionSnap(15);
|
thread1.getObject().removeTree(Lifespan.nowOn(16));
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals("15",
|
waitForPass(() -> {
|
||||||
threadsProvider.threadTableModel.getModelData().get(0).getDestructionSnap());
|
assertThreadRow(0, thread1.getObject(), "Processes[1].Threads[1]", 0L, 15L,
|
||||||
// NOTE: Plot max is based on time table, never thread destruction
|
TargetExecutionState.STOPPED, "A comment");
|
||||||
|
});
|
||||||
|
// NOTE: Destruction will not be visible in plot unless snapshot 15 is created
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -298,51 +450,57 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(2, threadsProvider.threadTableModel.getModelData().size());
|
waitForPass(() -> assertThreadsTableSize(2));
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
thread2.delete();
|
thread2.getObject().removeTree(Lifespan.ALL);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(1, threadsProvider.threadTableModel.getModelData().size());
|
waitForPass(() -> assertThreadsTableSize(1));
|
||||||
// NOTE: Plot max is based on time table, never thread destruction
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditThreadFields() throws Exception {
|
public void testEditThreadComment() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
|
int commentViewIdx =
|
||||||
|
provider.panel.getColumnByNameAndType("Comment", ValueProperty.class).getKey();
|
||||||
|
ObjectTableModel tableModel = QueryPanelTestHelper.getTableModel(provider.panel);
|
||||||
|
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
||||||
|
int commentModelIdx = table.convertColumnIndexToModel(commentViewIdx);
|
||||||
|
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
threadsProvider.threadTableModel.setValueAt("My Thread", 0,
|
tableModel.setValueAt(new ValueFixedProperty<>("A different comment"), 0,
|
||||||
ThreadTableColumns.NAME.ordinal());
|
commentModelIdx);
|
||||||
threadsProvider.threadTableModel.setValueAt("A different comment", 0,
|
|
||||||
ThreadTableColumns.COMMENT.ordinal());
|
|
||||||
});
|
});
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals("My Thread", thread1.getName());
|
waitForPass(() -> assertEquals("A different comment",
|
||||||
assertEquals("A different comment", thread1.getComment());
|
thread1.getObject().getAttribute(0, TraceObjectThread.KEY_COMMENT).getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertThreadsPopulated();
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
|
|
||||||
undo(tb.trace);
|
undo(tb.trace);
|
||||||
assertThreadsEmpty();
|
waitForTasks();
|
||||||
|
waitForPass(() -> assertThreadsEmpty());
|
||||||
|
|
||||||
redo(tb.trace);
|
redo(tb.trace);
|
||||||
assertThreadsPopulated();
|
waitForTasks();
|
||||||
|
waitForPass(() -> assertThreadsPopulated());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -350,15 +508,17 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
waitForPass(() -> {
|
||||||
assertThreadSelected(thread1);
|
assertThreadsPopulated();
|
||||||
|
assertThreadSelected(thread1);
|
||||||
|
});
|
||||||
|
|
||||||
traceManager.activateThread(thread2);
|
traceManager.activateThread(thread2);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadSelected(thread2);
|
waitForPass(() -> assertThreadSelected(thread2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -367,11 +527,15 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
waitForPass(() -> {
|
||||||
assertThreadSelected(thread1); // Manager selects default if not live
|
assertThreadsPopulated();
|
||||||
|
assertThreadSelected(thread1); // Manager selects default if not live
|
||||||
|
});
|
||||||
|
|
||||||
clickTableCellWithButton(threadsProvider.threadTable, 1, 0, MouseEvent.BUTTON1);
|
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
||||||
|
clickTableCellWithButton(table, 1, 0, MouseEvent.BUTTON1);
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
assertThreadSelected(thread2);
|
assertThreadSelected(thread2);
|
||||||
|
@ -384,85 +548,31 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertThreadsPopulated();
|
RangeCursorTableHeaderRenderer<Long> renderer =
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
QueryPanelTestHelper.getCursorHeaderRenderer(provider.panel);
|
||||||
assertEquals(0, threadsProvider.headerRenderer.getCursorPosition().longValue());
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertThreadsPopulated();
|
||||||
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
assertEquals(Long.valueOf(0), renderer.getCursorPosition());
|
||||||
|
});
|
||||||
|
|
||||||
traceManager.activateSnap(6);
|
traceManager.activateSnap(6);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(6, threadsProvider.headerRenderer.getCursorPosition().longValue());
|
waitForPass(() -> assertEquals(Long.valueOf(6), renderer.getCursorPosition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionStepTraceBackward() throws Exception {
|
public void testActionSeekTracePresent() throws Throwable {
|
||||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
assertTrue(provider.actionSeekTracePresent.isSelected());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
addThreads();
|
addThreads();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
tb.trace.getTimeManager().getSnapshot(10, true);
|
|
||||||
}
|
|
||||||
waitForDomainObject(tb.trace);
|
|
||||||
|
|
||||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
|
||||||
|
|
||||||
traceManager.activateSnap(2);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertTrue(threadsProvider.actionStepSnapBackward.isEnabled());
|
|
||||||
|
|
||||||
performAction(threadsProvider.actionStepSnapBackward);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(1, traceManager.getCurrentSnap());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActionStepTraceForward() throws Exception {
|
|
||||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
|
||||||
|
|
||||||
createAndOpenTrace();
|
|
||||||
addThreads();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
|
||||||
tb.trace.getTimeManager().getSnapshot(10, true);
|
|
||||||
}
|
|
||||||
waitForDomainObject(tb.trace);
|
|
||||||
|
|
||||||
assertTrue(threadsProvider.actionStepSnapForward.isEnabled());
|
|
||||||
|
|
||||||
performAction(threadsProvider.actionStepSnapForward);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(1, traceManager.getCurrentSnap());
|
|
||||||
assertTrue(threadsProvider.actionStepSnapForward.isEnabled());
|
|
||||||
|
|
||||||
traceManager.activateSnap(10);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActionSeekTracePresent() throws Exception {
|
|
||||||
assertTrue(threadsProvider.actionSeekTracePresent.isSelected());
|
|
||||||
|
|
||||||
createAndOpenTrace();
|
|
||||||
addThreads();
|
|
||||||
traceManager.activateTrace(tb.trace);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
|
@ -470,9 +580,10 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
tb.trace.getTimeManager().createSnapshot("Next snapshot");
|
tb.trace.getTimeManager().createSnapshot("Next snapshot");
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
// Not live, so no seek
|
// Not live, so no seek
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
waitForPass(() -> assertEquals(0, traceManager.getCurrentSnap()));
|
||||||
|
|
||||||
tb.close();
|
tb.close();
|
||||||
|
|
||||||
|
@ -481,27 +592,24 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
// Threads needs registers to be recognized by the recorder
|
// Threads needs registers to be recognized by the recorder
|
||||||
mb.createTestThreadRegisterBanks();
|
mb.createTestThreadRegisterBanks();
|
||||||
|
|
||||||
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
|
TraceRecorder recorder = recordAndWaitSync();
|
||||||
createTargetTraceMapper(mb.testProcess1));
|
traceManager.openTrace(tb.trace);
|
||||||
Trace trace = recorder.getTrace();
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
// Wait till two threads are observed in the database
|
|
||||||
waitForPass(() -> assertEquals(2, trace.getThreadManager().getAllThreads().size()));
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
TraceSnapshot snapshot = recorder.forceSnapshot();
|
TraceSnapshot snapshot = recorder.forceSnapshot();
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
|
waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
|
||||||
|
|
||||||
performAction(threadsProvider.actionSeekTracePresent);
|
performEnabledAction(provider, provider.actionSeekTracePresent, true);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertFalse(threadsProvider.actionSeekTracePresent.isSelected());
|
assertFalse(provider.actionSeekTracePresent.isSelected());
|
||||||
|
|
||||||
recorder.forceSnapshot();
|
recorder.forceSnapshot();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
|
waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -787,7 +787,12 @@ public interface TargetObjectSchema {
|
||||||
List<TargetObjectSchema> schemas = getSuccessorSchemas(path);
|
List<TargetObjectSchema> schemas = getSuccessorSchemas(path);
|
||||||
for (; path != null; path = PathUtils.parent(path)) {
|
for (; path != null; path = PathUtils.parent(path)) {
|
||||||
TargetObjectSchema schema = schemas.get(path.size());
|
TargetObjectSchema schema = schemas.get(path.size());
|
||||||
if (schema.getInterfaces().contains(type)) {
|
if (!schema.isCanonicalContainer()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TargetObjectSchema deSchema =
|
||||||
|
schema.getContext().getSchema(schema.getDefaultElementSchema());
|
||||||
|
if (deSchema.getInterfaces().contains(type)) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
List<String> inAgg = Private.searchForSuitableContainerInAggregate(schema, type);
|
List<String> inAgg = Private.searchForSuitableContainerInAggregate(schema, type);
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class HorizontalTabPanel<T> extends JPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final JList<T> list = new JList<>();
|
protected final JList<T> list = new JList<>();
|
||||||
private final JScrollPane scroll = new JScrollPane(list);
|
private final JScrollPane scroll = new JScrollPane(list);
|
||||||
private final JViewport viewport = scroll.getViewport();
|
private final JViewport viewport = scroll.getViewport();
|
||||||
private final DefaultListModel<T> model = new DefaultListModel<>();
|
private final DefaultListModel<T> model = new DefaultListModel<>();
|
||||||
|
|
|
@ -107,6 +107,10 @@ public class RangeCursorTableHeaderRenderer<N extends Number & Comparable<N>>
|
||||||
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener();
|
private final ForSeekMouseListener forSeekMouseListener = new ForSeekMouseListener();
|
||||||
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class);
|
private final ListenerSet<SeekListener> listeners = new ListenerSet<>(SeekListener.class);
|
||||||
|
|
||||||
|
public RangeCursorTableHeaderRenderer(N pos) {
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFullRange(Span<N, ?> fullRange) {
|
public void setFullRange(Span<N, ?> fullRange) {
|
||||||
this.fullRangeDouble = SpannedRenderer.validateViewRange(fullRange);
|
this.fullRangeDouble = SpannedRenderer.validateViewRange(fullRange);
|
||||||
|
|
|
@ -215,7 +215,7 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes
|
||||||
TableColumn column = table.getColumnModel().getColumn(MyColumns.LIFESPAN.ordinal());
|
TableColumn column = table.getColumnModel().getColumn(MyColumns.LIFESPAN.ordinal());
|
||||||
SpanTableCellRenderer<Integer> rangeRenderer = new SpanTableCellRenderer<>();
|
SpanTableCellRenderer<Integer> rangeRenderer = new SpanTableCellRenderer<>();
|
||||||
RangeCursorTableHeaderRenderer<Integer> headerRenderer =
|
RangeCursorTableHeaderRenderer<Integer> headerRenderer =
|
||||||
new RangeCursorTableHeaderRenderer<>();
|
new RangeCursorTableHeaderRenderer<>(0);
|
||||||
column.setCellRenderer(rangeRenderer);
|
column.setCellRenderer(rangeRenderer);
|
||||||
column.setHeaderRenderer(headerRenderer);
|
column.setHeaderRenderer(headerRenderer);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue