mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-5523: Allow tool-wide configuration of radix for displaying trace times.
This commit is contained in:
parent
92e2b6b5d4
commit
004712026b
38 changed files with 751 additions and 246 deletions
|
@ -39,6 +39,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
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.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.NotOwnerException;
|
import ghidra.util.NotOwnerException;
|
||||||
|
|
||||||
|
@ -712,7 +713,7 @@ public class DebuggerCoordinates {
|
||||||
coordState.putLong(KEY_THREAD_KEY, thread.getKey());
|
coordState.putLong(KEY_THREAD_KEY, thread.getKey());
|
||||||
}
|
}
|
||||||
if (time != null) {
|
if (time != null) {
|
||||||
coordState.putString(KEY_TIME, time.toString());
|
coordState.putString(KEY_TIME, time.toString(TimeRadix.DEC));
|
||||||
}
|
}
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
coordState.putInt(KEY_FRAME, frame);
|
coordState.putInt(KEY_FRAME, frame);
|
||||||
|
@ -785,7 +786,7 @@ public class DebuggerCoordinates {
|
||||||
String timeSpec = coordState.getString(KEY_TIME, null);
|
String timeSpec = coordState.getString(KEY_TIME, null);
|
||||||
TraceSchedule time;
|
TraceSchedule time;
|
||||||
try {
|
try {
|
||||||
time = TraceSchedule.parse(timeSpec);
|
time = TraceSchedule.parse(timeSpec, TimeRadix.DEC);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(DebuggerCoordinates.class,
|
Msg.error(DebuggerCoordinates.class,
|
||||||
|
|
|
@ -63,6 +63,7 @@ import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName;
|
||||||
import ghidra.trace.model.target.schema.XmlSchemaContext;
|
import ghidra.trace.model.target.schema.XmlSchemaContext;
|
||||||
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.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.DuplicateFileException;
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
|
@ -1166,8 +1167,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
|
||||||
TraceSnapshot snapshot = switch (req.getTimeCase()) {
|
TraceSnapshot snapshot = switch (req.getTimeCase()) {
|
||||||
case TIME_NOT_SET -> throw new TraceRmiError("snap or time required");
|
case TIME_NOT_SET -> throw new TraceRmiError("snap or time required");
|
||||||
case SNAP -> open.createSnapshot(req.getSnap().getSnap());
|
case SNAP -> open.createSnapshot(req.getSnap().getSnap());
|
||||||
case SCHEDULE -> open
|
case SCHEDULE -> open.createSnapshot(
|
||||||
.createSnapshot(TraceSchedule.parse(req.getSchedule().getSchedule()));
|
TraceSchedule.parse(req.getSchedule().getSchedule(), TimeRadix.DEC));
|
||||||
};
|
};
|
||||||
snapshot.setDescription(req.getDescription());
|
snapshot.setDescription(req.getDescription());
|
||||||
if (!"".equals(req.getDatetime())) {
|
if (!"".equals(req.getDatetime())) {
|
||||||
|
|
|
@ -101,5 +101,14 @@
|
||||||
emulated state into the trace's <EM>scratch space</EM>, which comprises all negative snaps.
|
emulated state into the trace's <EM>scratch space</EM>, which comprises all negative snaps.
|
||||||
Some time-travel capable back ends may also write into scratch space. When this toggle is
|
Some time-travel capable back ends may also write into scratch space. When this toggle is
|
||||||
enabled, those scratch snapshots are hidden.</P>
|
enabled, those scratch snapshots are hidden.</P>
|
||||||
|
|
||||||
|
<H3><A name="radix"></A>Set Time Radix</H3>
|
||||||
|
|
||||||
|
<P>These actions are available when a trace is active. It sets the display radix for snapshot
|
||||||
|
keys and time schedules <EM>throughout the tool</EM>. This is useful to match the display of
|
||||||
|
time coordinates with a back end that supports time travel. Notably, WinDbg TTD uses upper-case
|
||||||
|
hexadecimal for its event sequence numbers. Normally, the back end would set the UI's radix
|
||||||
|
automatically, but in case it does not, or if you'd like to override the radix, these actions
|
||||||
|
are available.</P>
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.memview;
|
package ghidra.app.plugin.core.debug.gui.memview;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import ghidra.async.AsyncDebouncer;
|
import ghidra.async.AsyncDebouncer;
|
||||||
|
@ -29,9 +28,11 @@ import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.breakpoint.*;
|
import ghidra.trace.model.breakpoint.*;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.memory.*;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.stack.*;
|
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
import ghidra.trace.model.thread.*;
|
import ghidra.trace.model.thread.*;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.trace.util.TraceEvents;
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
|
@ -84,6 +85,9 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
|
|
||||||
listenFor(TraceEvents.BYTES_CHANGED, this::bytesChanged);
|
listenFor(TraceEvents.BYTES_CHANGED, this::bytesChanged);
|
||||||
|
|
||||||
|
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
|
||||||
|
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
|
||||||
|
|
||||||
listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored);
|
listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +110,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
AddressRange rng = rng(defaultSpace, threadId, threadId);
|
AddressRange rng = rng(defaultSpace, threadId, threadId);
|
||||||
TraceObject obj = objThread.getObject();
|
TraceObject obj = objThread.getObject();
|
||||||
obj.getCanonicalParents(Lifespan.ALL).forEach(p -> {
|
obj.getCanonicalParents(Lifespan.ALL).forEach(p -> {
|
||||||
MemoryBox box = new MemoryBox("Thread " + thread.getName(p.getMinSnap()),
|
MemoryBox box = new MemoryBox(currentTrace, "Thread " + thread.getName(p.getMinSnap()),
|
||||||
MemviewBoxType.THREAD, rng, p.getLifespan());
|
MemviewBoxType.THREAD, rng, p.getLifespan());
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
});
|
});
|
||||||
|
@ -124,7 +128,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
if (region.getName(v.getMinSnap()).equals("full memory")) {
|
if (region.getName(v.getMinSnap()).equals("full memory")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MemoryBox box = new MemoryBox("Region " + region.getName(v.getMinSnap()),
|
MemoryBox box = new MemoryBox(currentTrace, "Region " + region.getName(v.getMinSnap()),
|
||||||
MemviewBoxType.REGION, v.castValue(), v.getLifespan());
|
MemviewBoxType.REGION, v.castValue(), v.getLifespan());
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
});
|
});
|
||||||
|
@ -138,7 +142,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
|
|
||||||
TraceObject obj = objModule.getObject();
|
TraceObject obj = objModule.getObject();
|
||||||
obj.getOrderedValues(Lifespan.ALL, TraceObjectModule.KEY_RANGE, true).forEach(v -> {
|
obj.getOrderedValues(Lifespan.ALL, TraceObjectModule.KEY_RANGE, true).forEach(v -> {
|
||||||
MemoryBox box = new MemoryBox("Module " + module.getName(v.getMinSnap()),
|
MemoryBox box = new MemoryBox(currentTrace, "Module " + module.getName(v.getMinSnap()),
|
||||||
MemviewBoxType.MODULE, v.castValue(), v.getLifespan());
|
MemviewBoxType.MODULE, v.castValue(), v.getLifespan());
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
});
|
});
|
||||||
|
@ -152,7 +156,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
|
|
||||||
TraceObject obj = objSection.getObject();
|
TraceObject obj = objSection.getObject();
|
||||||
obj.getOrderedValues(Lifespan.ALL, TraceObjectSection.KEY_RANGE, true).forEach(v -> {
|
obj.getOrderedValues(Lifespan.ALL, TraceObjectSection.KEY_RANGE, true).forEach(v -> {
|
||||||
MemoryBox box = new MemoryBox("Module " + section.getName(v.getMinSnap()),
|
MemoryBox box = new MemoryBox(currentTrace, "Module " + section.getName(v.getMinSnap()),
|
||||||
MemviewBoxType.IMAGE, v.castValue(), v.getLifespan());
|
MemviewBoxType.IMAGE, v.castValue(), v.getLifespan());
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
});
|
});
|
||||||
|
@ -168,7 +172,8 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
TraceObject obj = objBpt.getObject();
|
TraceObject obj = objBpt.getObject();
|
||||||
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
|
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
|
||||||
.forEach(v -> {
|
.forEach(v -> {
|
||||||
MemoryBox box = new MemoryBox("Module " + bpt.getName(v.getMinSnap()),
|
MemoryBox box =
|
||||||
|
new MemoryBox(currentTrace, "Module " + bpt.getName(v.getMinSnap()),
|
||||||
MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan());
|
MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan());
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
});
|
});
|
||||||
|
@ -180,12 +185,24 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Lifespan lifespan = range.getLifespan();
|
Lifespan lifespan = range.getLifespan();
|
||||||
MemoryBox box = new MemoryBox("BytesChanged " + range.description(),
|
MemoryBox box = new MemoryBox(currentTrace, "BytesChanged " + range.description(),
|
||||||
MemviewBoxType.WRITE_MEMORY, range.getRange(), lifespan);
|
MemviewBoxType.WRITE_MEMORY, range.getRange(), lifespan);
|
||||||
updateList.add(box);
|
updateList.add(box);
|
||||||
updateLabelDebouncer.contact(null);
|
updateLabelDebouncer.contact(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void valueCreated(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
provider.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void valueDeleted(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
provider.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void objectRestored(DomainObjectChangeRecord domainObjectChangeRecord) {
|
private void objectRestored(DomainObjectChangeRecord domainObjectChangeRecord) {
|
||||||
if (!trackTrace) {
|
if (!trackTrace) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -23,9 +23,12 @@ import java.util.Map;
|
||||||
import generic.theme.GThemeDefaults.Colors;
|
import generic.theme.GThemeDefaults.Colors;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
|
||||||
public class MemoryBox {
|
public class MemoryBox {
|
||||||
|
|
||||||
|
protected final Trace trace;
|
||||||
protected String id;
|
protected String id;
|
||||||
protected MemviewBoxType type;
|
protected MemviewBoxType type;
|
||||||
protected AddressRange range;
|
protected AddressRange range;
|
||||||
|
@ -46,7 +49,9 @@ public class MemoryBox {
|
||||||
|
|
||||||
protected boolean current;
|
protected boolean current;
|
||||||
|
|
||||||
public MemoryBox(String id, MemviewBoxType type, AddressRange range, long tick, Color color) {
|
public MemoryBox(Trace trace, String id, MemviewBoxType type, AddressRange range, long tick,
|
||||||
|
Color color) {
|
||||||
|
this.trace = trace;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.range = range;
|
this.range = range;
|
||||||
|
@ -54,12 +59,13 @@ public class MemoryBox {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryBox(String id, MemviewBoxType type, AddressRange range, long tick) {
|
public MemoryBox(Trace trace, String id, MemviewBoxType type, AddressRange range, long tick) {
|
||||||
this(id, type, range, tick, type.getColor());
|
this(trace, id, type, range, tick, type.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryBox(String id, MemviewBoxType type, AddressRange range, Lifespan trange) {
|
public MemoryBox(Trace trace, String id, MemviewBoxType type, AddressRange range,
|
||||||
this(id, type, range, trange.lmin());
|
Lifespan trange) {
|
||||||
|
this(trace, id, type, range, trange.lmin());
|
||||||
setEnd(trange.lmax());
|
setEnd(trange.lmax());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +230,18 @@ public class MemoryBox {
|
||||||
stopTime = val;
|
stopTime = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TimeRadix getTimeRadix() {
|
||||||
|
return trace.getTimeManager().getTimeRadix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatStart() {
|
||||||
|
return getTimeRadix().format(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatEnd() {
|
||||||
|
return getTimeRadix().format(stop);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean inPixelRange(long pos) {
|
public boolean inPixelRange(long pos) {
|
||||||
if (pos < pixTstart) {
|
if (pos < pixTstart) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -38,7 +38,8 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
|
||||||
private Map<String, MemoryBox> memMap = new HashMap<>();
|
private Map<String, MemoryBox> memMap = new HashMap<>();
|
||||||
private MemviewProvider provider;
|
private MemviewProvider provider;
|
||||||
|
|
||||||
private final static String COLUMN_NAMES[] = { NAME_COL, ASTART_COL, ASTOP_COL, TSTART_COL, TSTOP_COL };
|
private final static String COLUMN_NAMES[] =
|
||||||
|
{ NAME_COL, ASTART_COL, ASTOP_COL, TSTART_COL, TSTOP_COL };
|
||||||
|
|
||||||
public MemviewMapModel(MemviewProvider provider) {
|
public MemviewMapModel(MemviewProvider provider) {
|
||||||
super(ASTART);
|
super(ASTART);
|
||||||
|
@ -108,9 +109,9 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for locating columns by name. Implementation is naive so
|
* Convenience method for locating columns by name. Implementation is naive so this should be
|
||||||
* this should be overridden if this method is to be called often. This method
|
* overridden if this method is to be called often. This method is not in the TableModel
|
||||||
* is not in the TableModel interface and is not used by the JTable.
|
* interface and is not used by the JTable.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int findColumn(String columnName) {
|
public int findColumn(String columnName) {
|
||||||
|
@ -142,10 +143,9 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of records managed by the data source object. A
|
* Returns the number of records managed by the data source object. A <B>JTable</B> uses this
|
||||||
* <B>JTable</B> uses this method to determine how many rows it should create
|
* method to determine how many rows it should create and display. This method should be quick,
|
||||||
* and display. This method should be quick, as it is call by <B>JTable</B>
|
* as it is call by <B>JTable</B> quite frequently.
|
||||||
* quite frequently.
|
|
||||||
*
|
*
|
||||||
* @return the number or rows in the model
|
* @return the number or rows in the model
|
||||||
* @see #getColumnCount
|
* @see #getColumnCount
|
||||||
|
@ -165,7 +165,8 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
|
||||||
MemoryBox box = memList.get(rowIndex);
|
MemoryBox box = memList.get(rowIndex);
|
||||||
try {
|
try {
|
||||||
box.getStart();
|
box.getStart();
|
||||||
} catch (ConcurrentModificationException e) {
|
}
|
||||||
|
catch (ConcurrentModificationException e) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
return memList.get(rowIndex);
|
return memList.get(rowIndex);
|
||||||
|
@ -186,17 +187,18 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
|
||||||
case ASTOP:
|
case ASTOP:
|
||||||
return box.getRange().getMaxAddress();
|
return box.getRange().getMaxAddress();
|
||||||
case TSTART:
|
case TSTART:
|
||||||
return Long.toString(box.getStart(), 16);
|
return box.formatStart();
|
||||||
case TSTOP:
|
case TSTOP:
|
||||||
long end = box.getEnd();
|
long end = box.getEnd();
|
||||||
if (end == Long.MAX_VALUE) {
|
if (end == Long.MAX_VALUE) {
|
||||||
return "+" + '\u221e' + '\u2025';
|
return "+" + '\u221e' + '\u2025';
|
||||||
}
|
}
|
||||||
return Long.toString(end, 16);
|
return box.formatEnd();
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
} catch (ConcurrentModificationException e) {
|
}
|
||||||
|
catch (ConcurrentModificationException e) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import generic.theme.GColor;
|
||||||
import generic.theme.GThemeDefaults.Colors;
|
import generic.theme.GThemeDefaults.Colors;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressRange;
|
import ghidra.program.model.address.AddressRange;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
|
||||||
public class MemviewPanel extends JPanel implements MouseListener, MouseMotionListener {
|
public class MemviewPanel extends JPanel implements MouseListener, MouseMotionListener {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
@ -484,11 +485,19 @@ public class MemviewPanel extends JPanel implements MouseListener, MouseMotionLi
|
||||||
return aval;
|
return aval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TimeRadix getTimeRadix() {
|
||||||
|
if (boxList == null || boxList.isEmpty()) {
|
||||||
|
return TimeRadix.DEFAULT;
|
||||||
|
}
|
||||||
|
return boxList.get(0).trace.getTimeManager().getTimeRadix();
|
||||||
|
}
|
||||||
|
|
||||||
public String getTagForTick(long tick) {
|
public String getTagForTick(long tick) {
|
||||||
|
TimeRadix radix = getTimeRadix();
|
||||||
String tval = "";
|
String tval = "";
|
||||||
if (0 <= tick && tick < timesArray.length) {
|
if (0 <= tick && tick < timesArray.length) {
|
||||||
Long time = timesArray[(int) tick];
|
Long time = timesArray[(int) tick];
|
||||||
tval = Long.toString(time, 16);
|
tval = radix.format(time);
|
||||||
}
|
}
|
||||||
return tval;
|
return tval;
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,4 +306,10 @@ public class MemviewProvider extends ComponentProviderAdapter {
|
||||||
memviewPanel.reset();
|
memviewPanel.reset();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fireTableDataChanged() {
|
||||||
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
|
memviewTable.fireTableDataChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,10 @@ public class MemviewTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fireTableDataChanged() {
|
||||||
|
model.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private List<MemviewRow> generateRows(Collection<MemoryBox> changed) {
|
private List<MemviewRow> generateRows(Collection<MemoryBox> changed) {
|
||||||
List<MemviewRow> list = new ArrayList<>();
|
List<MemviewRow> list = new ArrayList<>();
|
||||||
|
|
|
@ -28,6 +28,8 @@ import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.trace.util.TraceEvents;
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
@ -56,12 +58,20 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
||||||
if (query != null && query.involves(span, value)) {
|
if (query != null && query.involves(span, value)) {
|
||||||
reload(); // Can I be more surgical?
|
reload(); // Can I be more surgical?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void valueDeleted(TraceObjectValue value) {
|
protected void valueDeleted(TraceObjectValue value) {
|
||||||
if (query != null && query.involves(span, value)) {
|
if (query != null && query.involves(span, value)) {
|
||||||
reload(); // Can I be more surgical?
|
reload(); // Can I be more surgical?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void valueLifespanChanged(TraceObjectValue value, Lifespan oldSpan,
|
protected void valueLifespanChanged(TraceObjectValue value, Lifespan oldSpan,
|
||||||
|
|
|
@ -20,20 +20,34 @@ import java.awt.Component;
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
import docking.widgets.table.GTableCellRenderingData;
|
import docking.widgets.table.GTableCellRenderingData;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.columns.TracePathLastLifespanColumn.SpanAndRadix;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TracePathLastLifespanColumn
|
public class TracePathLastLifespanColumn
|
||||||
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> {
|
extends AbstractDynamicTableColumn<PathRow, SpanAndRadix, Trace> {
|
||||||
|
|
||||||
private final class LastLifespanRenderer extends AbstractGColumnRenderer<Lifespan> {
|
record SpanAndRadix(Lifespan span, TimeRadix radix) implements Comparable<SpanAndRadix> {
|
||||||
@Override
|
@Override
|
||||||
public String getFilterString(Lifespan t, Settings settings) {
|
public final String toString() {
|
||||||
|
return span.toString(radix::format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(SpanAndRadix that) {
|
||||||
|
return this.span.compareTo(that.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class LastLifespanRenderer extends AbstractGColumnRenderer<SpanAndRadix> {
|
||||||
|
@Override
|
||||||
|
public String getFilterString(SpanAndRadix t, Settings settings) {
|
||||||
return t == null ? "<null>" : t.toString();
|
return t == null ? "<null>" : t.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,17 +70,18 @@ public class TracePathLastLifespanColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GColumnRenderer<Lifespan> getColumnRenderer() {
|
public GColumnRenderer<SpanAndRadix> getColumnRenderer() {
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
|
public SpanAndRadix getValue(PathRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
TraceObjectValue lastEntry = rowObject.getPath().getLastEntry();
|
TraceObjectValue lastEntry = rowObject.getPath().getLastEntry();
|
||||||
|
TimeRadix radix = data.getTimeManager().getTimeRadix();
|
||||||
if (lastEntry == null) {
|
if (lastEntry == null) {
|
||||||
return Lifespan.ALL;
|
return new SpanAndRadix(Lifespan.ALL, radix);
|
||||||
}
|
}
|
||||||
return lastEntry.getLifespan();
|
return new SpanAndRadix(lastEntry.getLifespan(), radix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,25 @@ package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueLifeColumn.SetAndRadix;
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.trace.model.Lifespan.LifeSet;
|
import ghidra.trace.model.Lifespan.LifeSet;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
public class TraceValueLifeColumn
|
public class TraceValueLifeColumn
|
||||||
extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> {
|
extends AbstractDynamicTableColumn<ValueRow, SetAndRadix, Trace> {
|
||||||
private final TraceValueColumnRenderer<LifeSet> renderer = new TraceValueColumnRenderer<>();
|
|
||||||
|
record SetAndRadix(LifeSet set, TimeRadix radix) {
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return set.toString(radix::format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TraceValueColumnRenderer<SetAndRadix> renderer = new TraceValueColumnRenderer<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -33,13 +43,13 @@ public class TraceValueLifeColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GColumnRenderer<LifeSet> getColumnRenderer() {
|
public GColumnRenderer<SetAndRadix> getColumnRenderer() {
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data,
|
public SetAndRadix getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||||
return rowObject.getLife();
|
return new SetAndRadix(rowObject.getLife(), data.getTimeManager().getTimeRadix());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,13 @@ import ghidra.program.model.listing.Instruction;
|
||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
@ -505,6 +511,25 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ForRadixTraceListener extends TraceDomainObjectListener {
|
||||||
|
{
|
||||||
|
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
|
||||||
|
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void valueCreated(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
updateSubTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void valueDeleted(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
updateSubTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static String createColoredStyle(String cls, Color color) {
|
protected static String createColoredStyle(String cls, Color color) {
|
||||||
if (color == null) {
|
if (color == null) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -525,6 +550,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final TraceDomainObjectListener forRadixTraceListener = new ForRadixTraceListener();
|
||||||
|
|
||||||
private final DebuggerPcodeStepperPlugin plugin;
|
private final DebuggerPcodeStepperPlugin plugin;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
@ -693,18 +720,36 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void removeTraceListener() {
|
||||||
|
if (current.getTrace() != null) {
|
||||||
|
current.getTrace().removeListener(forRadixTraceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addTraceListener() {
|
||||||
|
if (current.getTrace() != null) {
|
||||||
|
current.getTrace().addListener(forRadixTraceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateSubTitle() {
|
||||||
|
TimeRadix radix = current.getTrace().getTimeManager().getTimeRadix();
|
||||||
|
setSubTitle(current.getTime().toString(radix));
|
||||||
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
if (sameCoordinates(current, coordinates)) {
|
if (sameCoordinates(current, coordinates)) {
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
previous = current;
|
previous = current;
|
||||||
|
removeTraceListener();
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
addTraceListener();
|
||||||
|
|
||||||
doLoadPcodeFrame();
|
doLoadPcodeFrame();
|
||||||
|
updateSubTitle();
|
||||||
setSubTitle(current.getTime().toString());
|
|
||||||
|
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,14 @@ import ghidra.framework.model.DomainObjectEvent;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
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.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.trace.util.TraceEvents;
|
import ghidra.trace.util.TraceEvents;
|
||||||
|
import ghidra.util.DateUtils;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
|
||||||
|
@ -46,10 +50,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
||||||
SNAP("Snap", Long.class, SnapshotRow::getSnap, false),
|
SNAP("Snap", Long.class, SnapshotRow::getSnap, false),
|
||||||
TIME("Time", TraceSchedule.class, SnapshotRow::getTime, true),
|
TIME("Time", TraceSchedule.class, SnapshotRow::getTime, true),
|
||||||
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp, true), // TODO: Use Date type here
|
TIMESTAMP("Timestamp", Date.class, SnapshotRow::getTimeStamp, true),
|
||||||
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName, true),
|
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName, true),
|
||||||
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule, false),
|
SCHEDULE("Schedule", TraceSchedule.class, SnapshotRow::getSchedule, false),
|
||||||
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription, true);
|
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, //
|
||||||
|
SnapshotRow::setDescription, true);
|
||||||
|
|
||||||
private final String header;
|
private final String header;
|
||||||
private final Function<SnapshotRow, ?> getter;
|
private final Function<SnapshotRow, ?> getter;
|
||||||
|
@ -122,6 +127,9 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
listenFor(TraceEvents.SNAPSHOT_ADDED, this::snapAdded);
|
listenFor(TraceEvents.SNAPSHOT_ADDED, this::snapAdded);
|
||||||
listenFor(TraceEvents.SNAPSHOT_CHANGED, this::snapChanged);
|
listenFor(TraceEvents.SNAPSHOT_CHANGED, this::snapChanged);
|
||||||
listenFor(TraceEvents.SNAPSHOT_DELETED, this::snapDeleted);
|
listenFor(TraceEvents.SNAPSHOT_DELETED, this::snapDeleted);
|
||||||
|
|
||||||
|
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
|
||||||
|
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void objectRestored() {
|
private void objectRestored() {
|
||||||
|
@ -132,7 +140,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
if (snapshot.getKey() < 0 && hideScratch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SnapshotRow row = new SnapshotRow(currentTrace, snapshot);
|
SnapshotRow row = new SnapshotRow(snapshot);
|
||||||
snapshotTableModel.add(row);
|
snapshotTableModel.add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,12 +157,50 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
}
|
}
|
||||||
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void valueCreated(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
snapshotTableModel.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void valueDeleted(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
snapshotTableModel.fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final TableCellRenderer styleCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
final TableCellRenderer styleCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||||
|
@Override
|
||||||
|
protected String formatNumber(Number value, Settings settings) {
|
||||||
|
return switch (value) {
|
||||||
|
case null -> "<null>";
|
||||||
|
// SNAP is the only column with Long type
|
||||||
|
case Long snap -> getTimeRadix().format(snap);
|
||||||
|
default -> super.formatNumber(value, settings);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getText(Object value) {
|
||||||
|
return switch (value) {
|
||||||
|
case null -> "<null>";
|
||||||
|
case Date date -> DateUtils.formatDateTimestamp(date);
|
||||||
|
case TraceSchedule schedule -> schedule.toString(getTimeRadix());
|
||||||
|
default -> value.toString();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilterString(Object t, Settings settings) {
|
public String getFilterString(Object t, Settings settings) {
|
||||||
return t == null ? "<null>" : t.toString();
|
return switch (t) {
|
||||||
|
case null -> "<null>";
|
||||||
|
// SNAP is the only column with Long type
|
||||||
|
case Long snap -> getTimeRadix().format(snap);
|
||||||
|
case Number n -> formatNumber(n, settings);
|
||||||
|
default -> getText(t);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -216,6 +262,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
descCol.setCellRenderer(styleCurrentRenderer);
|
descCol.setCellRenderer(styleCurrentRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TimeRadix getTimeRadix() {
|
||||||
|
return currentTrace == null ? TimeRadix.DEFAULT
|
||||||
|
: currentTrace.getTimeManager().getTimeRadix();
|
||||||
|
}
|
||||||
|
|
||||||
private void addNewListeners() {
|
private void addNewListeners() {
|
||||||
if (currentTrace == null) {
|
if (currentTrace == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -268,7 +319,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
for (TraceSnapshot snapshot : hideScratch
|
for (TraceSnapshot snapshot : hideScratch
|
||||||
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
||||||
: manager.getAllSnapshots()) {
|
: manager.getAllSnapshots()) {
|
||||||
SnapshotRow row = new SnapshotRow(currentTrace, snapshot);
|
SnapshotRow row = new SnapshotRow(snapshot);
|
||||||
toAdd.add(row);
|
toAdd.add(row);
|
||||||
if (current != DebuggerCoordinates.NOWHERE &&
|
if (current != DebuggerCoordinates.NOWHERE &&
|
||||||
snapshot.getKey() == current.getViewSnap()) {
|
snapshot.getKey() == current.getViewSnap()) {
|
||||||
|
@ -289,7 +340,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||||
Collection<? extends TraceSnapshot> sratch =
|
Collection<? extends TraceSnapshot> sratch =
|
||||||
manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
|
manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
|
||||||
snapshotTableModel.addAll(sratch.stream()
|
snapshotTableModel.addAll(sratch.stream()
|
||||||
.map(s -> new SnapshotRow(currentTrace, s))
|
.map(s -> new SnapshotRow(s))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,11 @@ import java.lang.invoke.MethodHandles;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
|
import db.Transaction;
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
|
import docking.action.builder.ToggleActionBuilder;
|
||||||
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.DebuggerSnapActionContext;
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
|
@ -37,8 +39,14 @@ import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
|
import ghidra.trace.model.target.path.KeyPath;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
@ -54,7 +62,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
static ActionBuilder builder(Plugin owner) {
|
static ActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
||||||
.menuGroup(GROUP)
|
.menuGroup(GROUP)
|
||||||
.menuIcon(ICON)
|
.menuIcon(ICON)
|
||||||
|
@ -63,6 +72,43 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SetTimeRadixAction {
|
||||||
|
String NAME = "Set Time Radix";
|
||||||
|
String DESCRIPTION = "Change the time radix for this trace / target";
|
||||||
|
String GROUP = GROUP_TRACE;
|
||||||
|
String HELP_ANCHOR = "radix";
|
||||||
|
|
||||||
|
static ToggleActionBuilder builder(String title, Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ToggleActionBuilder(NAME + " - " + title, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.menuPath(DebuggerPluginPackage.NAME, NAME, title)
|
||||||
|
.menuGroup(GROUP)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ForRadixTraceListener extends TraceDomainObjectListener {
|
||||||
|
{
|
||||||
|
listenFor(TraceEvents.VALUE_CREATED, this::valueCreated);
|
||||||
|
listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void valueCreated(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
refreshRadixSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void valueDeleted(TraceObjectValue value) {
|
||||||
|
if (value.getCanonicalPath().equals(KeyPath.of(TraceTimeManager.KEY_TIME_RADIX))) {
|
||||||
|
refreshRadixSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TraceDomainObjectListener forRadixTraceListener = new ForRadixTraceListener();
|
||||||
|
|
||||||
protected final DebuggerTimePlugin plugin;
|
protected final DebuggerTimePlugin plugin;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
|
@ -78,6 +124,9 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
DockingAction actionGoToTime;
|
DockingAction actionGoToTime;
|
||||||
ToggleDockingAction actionHideScratch;
|
ToggleDockingAction actionHideScratch;
|
||||||
|
ToggleDockingAction actionSetRadixDec;
|
||||||
|
ToggleDockingAction actionSetRadixHexUpper;
|
||||||
|
ToggleDockingAction actionSetRadixHexLower;
|
||||||
|
|
||||||
private DebuggerSnapActionContext myActionContext;
|
private DebuggerSnapActionContext myActionContext;
|
||||||
|
|
||||||
|
@ -202,6 +251,21 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
.selected(hideScratch)
|
.selected(hideScratch)
|
||||||
.onAction(this::activatedHideScratch)
|
.onAction(this::activatedHideScratch)
|
||||||
.buildAndInstallLocal(this);
|
.buildAndInstallLocal(this);
|
||||||
|
actionSetRadixDec = SetTimeRadixAction.builder("Decimal", plugin)
|
||||||
|
.enabledWhen(c -> current.getTrace() != null &&
|
||||||
|
current.getTrace().getObjectManager().getRootObject() != null)
|
||||||
|
.onAction(c -> activatedSetRadix(TimeRadix.DEC))
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
actionSetRadixHexUpper = SetTimeRadixAction.builder("Upper Hex", plugin)
|
||||||
|
.enabledWhen(c -> current.getTrace() != null &&
|
||||||
|
current.getTrace().getObjectManager().getRootObject() != null)
|
||||||
|
.onAction(c -> activatedSetRadix(TimeRadix.HEX_UPPER))
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
actionSetRadixHexLower = SetTimeRadixAction.builder("Lower Hex", plugin)
|
||||||
|
.enabledWhen(c -> current.getTrace() != null &&
|
||||||
|
current.getTrace().getObjectManager().getRootObject() != null)
|
||||||
|
.onAction(c -> activatedSetRadix(TimeRadix.HEX_LOWER))
|
||||||
|
.buildAndInstall(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activatedGoToTime() {
|
private void activatedGoToTime() {
|
||||||
|
@ -218,10 +282,42 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
mainPanel.setHideScratchSnapshots(hideScratch);
|
mainPanel.setHideScratchSnapshots(hideScratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activatedSetRadix(TimeRadix radix) {
|
||||||
|
try (Transaction tx = current.getTrace().openTransaction("Set Time Radix")) {
|
||||||
|
current.getTrace().getTimeManager().setTimeRadix(radix);
|
||||||
|
}
|
||||||
|
// NOTE: refreshRadixSelection() should happen via listener
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeTraceListener() {
|
||||||
|
if (current.getTrace() != null) {
|
||||||
|
current.getTrace().removeListener(forRadixTraceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addTraceListener() {
|
||||||
|
if (current.getTrace() != null) {
|
||||||
|
current.getTrace().addListener(forRadixTraceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
removeTraceListener();
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
addTraceListener();
|
||||||
|
|
||||||
mainPanel.setTrace(current.getTrace());
|
mainPanel.setTrace(current.getTrace());
|
||||||
mainPanel.setCurrent(current);
|
mainPanel.setCurrent(current);
|
||||||
|
|
||||||
|
refreshRadixSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshRadixSelection() {
|
||||||
|
TimeRadix radix = current.getTrace() == null ? TimeRadix.DEFAULT
|
||||||
|
: current.getTrace().getTimeManager().getTimeRadix();
|
||||||
|
actionSetRadixHexLower.setSelected(radix == TimeRadix.HEX_LOWER);
|
||||||
|
actionSetRadixHexUpper.setSelected(radix == TimeRadix.HEX_UPPER);
|
||||||
|
actionSetRadixDec.setSelected(radix == TimeRadix.DEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeConfigState(SaveState saveState) {
|
public void writeConfigState(SaveState saveState) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.MessageType;
|
import ghidra.util.MessageType;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
DebuggerSnapshotTablePanel snapshotPanel;
|
DebuggerSnapshotTablePanel snapshotPanel;
|
||||||
JTextField scheduleText;
|
JTextField scheduleText;
|
||||||
|
TimeRadix radix = TimeRadix.DEFAULT;
|
||||||
TraceSchedule schedule;
|
TraceSchedule schedule;
|
||||||
|
|
||||||
JButton tickStep;
|
JButton tickStep;
|
||||||
|
@ -56,7 +58,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
if (stepped == null) {
|
if (stepped == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setScheduleText(stepped.toString());
|
setScheduleText(stepped.toString(radix));
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
Msg.warn(this, e.getMessage());
|
Msg.warn(this, e.getMessage());
|
||||||
|
@ -102,7 +104,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
if (schedule.getSnap() == snap.longValue()) {
|
if (schedule.getSnap() == snap.longValue()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduleText.setText(snap.toString());
|
scheduleText.setText(radix.format(snap));
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduleText.getDocument().addDocumentListener(new DocumentListener() {
|
scheduleText.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
@ -132,7 +134,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
protected void scheduleTextChanged() {
|
protected void scheduleTextChanged() {
|
||||||
schedule = null;
|
schedule = null;
|
||||||
try {
|
try {
|
||||||
schedule = TraceSchedule.parse(scheduleText.getText());
|
schedule = TraceSchedule.parse(scheduleText.getText(), radix);
|
||||||
snapshotPanel.setSelectedSnapshot(schedule.getSnap());
|
snapshotPanel.setSelectedSnapshot(schedule.getSnap());
|
||||||
schedule.validate(getTrace());
|
schedule.validate(getTrace());
|
||||||
setStatusText("");
|
setStatusText("");
|
||||||
|
@ -169,6 +171,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
public void close() {
|
public void close() {
|
||||||
super.close();
|
super.close();
|
||||||
snapshotPanel.setTrace(null);
|
snapshotPanel.setTrace(null);
|
||||||
|
radix = TimeRadix.DEFAULT;
|
||||||
snapshotPanel.setSelectedSnapshot(null);
|
snapshotPanel.setSelectedSnapshot(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,13 +179,14 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
|
||||||
* Prompts the user to select a snapshot and optionally specify a full schedule
|
* Prompts the user to select a snapshot and optionally specify a full schedule
|
||||||
*
|
*
|
||||||
* @param trace the trace from whose snapshots to select
|
* @param trace the trace from whose snapshots to select
|
||||||
* @param defaultTime, optionally the time to select initially
|
* @param defaultTime optionally the time to select initially
|
||||||
* @return the schedule, likely specifying just the snapshot selection
|
* @return the schedule, likely specifying just the snapshot selection
|
||||||
*/
|
*/
|
||||||
public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) {
|
public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) {
|
||||||
snapshotPanel.setTrace(trace);
|
snapshotPanel.setTrace(trace);
|
||||||
|
radix = trace.getTimeManager().getTimeRadix();
|
||||||
schedule = defaultTime;
|
schedule = defaultTime;
|
||||||
scheduleText.setText(defaultTime.toString());
|
scheduleText.setText(defaultTime.toString(radix));
|
||||||
tool.showDialog(this);
|
tool.showDialog(this);
|
||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,14 @@ import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
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.DateUtils;
|
|
||||||
|
|
||||||
public class SnapshotRow {
|
public class SnapshotRow {
|
||||||
//private static final DateFormat FORMAT = DateFormat.getDateTimeInstance();
|
|
||||||
|
|
||||||
private final Trace trace;
|
|
||||||
private final TraceSnapshot snapshot;
|
private final TraceSnapshot snapshot;
|
||||||
|
private final Trace trace;
|
||||||
|
|
||||||
public SnapshotRow(Trace trace, TraceSnapshot snapshot) {
|
public SnapshotRow(TraceSnapshot snapshot) {
|
||||||
this.trace = snapshot.getTrace();
|
|
||||||
this.snapshot = snapshot;
|
this.snapshot = snapshot;
|
||||||
|
this.trace = snapshot.getTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceSnapshot getSnapshot() {
|
public TraceSnapshot getSnapshot() {
|
||||||
|
@ -51,8 +48,8 @@ public class SnapshotRow {
|
||||||
return snapshot.getKey();
|
return snapshot.getKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTimeStamp() {
|
public Date getTimeStamp() {
|
||||||
return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime()));
|
return new Date(snapshot.getRealTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEventThreadName() {
|
public String getEventThreadName() {
|
||||||
|
@ -60,8 +57,8 @@ public class SnapshotRow {
|
||||||
return thread == null ? "" : thread.getName(snapshot.getKey());
|
return thread == null ? "" : thread.getName(snapshot.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSchedule() {
|
public TraceSchedule getSchedule() {
|
||||||
return snapshot.getScheduleString();
|
return snapshot.getSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
|
|
|
@ -27,11 +27,11 @@ import docking.action.builder.ActionBuilder;
|
||||||
import generic.theme.GThemeDefaults.Colors;
|
import generic.theme.GThemeDefaults.Colors;
|
||||||
import ghidra.app.plugin.core.debug.gui.timeoverview.*;
|
import ghidra.app.plugin.core.debug.gui.timeoverview.*;
|
||||||
import ghidra.app.plugin.core.overview.OverviewColorLegendDialog;
|
import ghidra.app.plugin.core.overview.OverviewColorLegendDialog;
|
||||||
import ghidra.app.plugin.core.overview.OverviewColorPlugin;
|
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
|
|
||||||
public class TimeTypeOverviewColorService implements TimeOverviewColorService {
|
public class TimeTypeOverviewColorService implements TimeOverviewColorService {
|
||||||
|
@ -82,12 +82,13 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService {
|
||||||
if (snap == null) {
|
if (snap == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
TimeRadix radix = trace == null ? TimeRadix.DEFAULT : trace.getTimeManager().getTimeRadix();
|
||||||
Set<Pair<TimeType, String>> types = plugin.getTypes(snap);
|
Set<Pair<TimeType, String>> types = plugin.getTypes(snap);
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
buffer.append("<b>");
|
buffer.append("<b>");
|
||||||
buffer.append(HTMLUtilities.escapeHTML(getName()));
|
buffer.append(HTMLUtilities.escapeHTML(getName()));
|
||||||
buffer.append(" (");
|
buffer.append(" (");
|
||||||
buffer.append(Long.toHexString(snap));
|
buffer.append(radix.format(snap));
|
||||||
buffer.append(")");
|
buffer.append(")");
|
||||||
buffer.append("</b>\n");
|
buffer.append("</b>\n");
|
||||||
for (Pair<TimeType, String> pair : types) {
|
for (Pair<TimeType, String> pair : types) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginEventListener;
|
import ghidra.framework.plugintool.util.PluginEventListener;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import utilities.util.SuppressableCallback;
|
import utilities.util.SuppressableCallback;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
@ -123,10 +124,11 @@ public class DebuggerTraceTabPanel extends GTabPanel<Trace>
|
||||||
DebuggerCoordinates current =
|
DebuggerCoordinates current =
|
||||||
traceManager == null ? DebuggerCoordinates.NOWHERE : traceManager.getCurrentFor(trace);
|
traceManager == null ? DebuggerCoordinates.NOWHERE : traceManager.getCurrentFor(trace);
|
||||||
if (current == DebuggerCoordinates.NOWHERE) {
|
if (current == DebuggerCoordinates.NOWHERE) {
|
||||||
// TODO: Could use view's snap and time table's schedule
|
// NOTE: Could use view's snap and time table's schedule, but not worth it.
|
||||||
return name + " (?)";
|
return name + " (?)";
|
||||||
}
|
}
|
||||||
String schedule = current.getTime().toString();
|
TimeRadix radix = trace.getTimeManager().getTimeRadix();
|
||||||
|
String schedule = current.getTime().toString(radix);
|
||||||
if (schedule.length() > 15) {
|
if (schedule.length() > 15) {
|
||||||
schedule = "..." + schedule.substring(schedule.length() - 12);
|
schedule = "..." + schedule.substring(schedule.length() - 12);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
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.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.*;
|
import ghidra.util.database.*;
|
||||||
|
@ -85,7 +86,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
|
||||||
eventThread = manager.threadManager.getThread(threadKey);
|
eventThread = manager.threadManager.getThread(threadKey);
|
||||||
if (!"".equals(scheduleStr)) {
|
if (!"".equals(scheduleStr)) {
|
||||||
try {
|
try {
|
||||||
schedule = TraceSchedule.parse(scheduleStr);
|
schedule = TraceSchedule.parse(scheduleStr, TimeRadix.DEC);
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
Msg.error(this, "Could not parse schedule: " + schedule, e);
|
Msg.error(this, "Could not parse schedule: " + schedule, e);
|
||||||
|
@ -205,7 +206,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
|
||||||
public void setSchedule(TraceSchedule schedule) {
|
public void setSchedule(TraceSchedule schedule) {
|
||||||
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
|
||||||
this.schedule = schedule;
|
this.schedule = schedule;
|
||||||
this.scheduleStr = schedule == null ? "" : schedule.toString();
|
this.scheduleStr = schedule == null ? "" : schedule.toString(TimeRadix.DEC);
|
||||||
update(SCHEDULE_COLUMN);
|
update(SCHEDULE_COLUMN);
|
||||||
manager.notifySnapshotChanged(this);
|
manager.notifySnapshotChanged(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,14 @@ import db.DBHandle;
|
||||||
import ghidra.framework.data.OpenMode;
|
import ghidra.framework.data.OpenMode;
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.database.DBTraceManager;
|
import ghidra.trace.database.DBTraceManager;
|
||||||
|
import ghidra.trace.database.target.DBTraceObject;
|
||||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||||
|
import ghidra.trace.model.Lifespan;
|
||||||
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
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.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.trace.util.TraceEvents;
|
import ghidra.trace.util.TraceEvents;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
|
@ -181,4 +185,34 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
|
||||||
notifySnapshotDeleted(snapshot);
|
notifySnapshotDeleted(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimeRadix(TimeRadix radix) {
|
||||||
|
DBTraceObject root = trace.getObjectManager().getRootObject();
|
||||||
|
if (root == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"There must be a root object in the ObjectManager before setting the TimeRadix");
|
||||||
|
}
|
||||||
|
root.setAttribute(Lifespan.ALL, KEY_TIME_RADIX, switch (radix) {
|
||||||
|
case DEC -> "dec";
|
||||||
|
case HEX_UPPER -> "HEX";
|
||||||
|
case HEX_LOWER -> "hex";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeRadix getTimeRadix() {
|
||||||
|
DBTraceObject root = trace.getObjectManager().getRootObject();
|
||||||
|
if (root == null) {
|
||||||
|
return TimeRadix.DEFAULT;
|
||||||
|
}
|
||||||
|
TraceObjectValue attribute = root.getAttribute(0, KEY_TIME_RADIX);
|
||||||
|
if (attribute == null) {
|
||||||
|
return TimeRadix.DEFAULT;
|
||||||
|
}
|
||||||
|
return switch (attribute.getValue()) {
|
||||||
|
case String s -> TimeRadix.fromStr(s);
|
||||||
|
default -> TimeRadix.DEFAULT;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,7 +281,7 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -329,7 +329,7 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
|
||||||
public record Impl(long lmin, long lmax) implements Lifespan {
|
public record Impl(long lmin, long lmax) implements Lifespan {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,8 +18,12 @@ package ghidra.trace.model.time;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
|
||||||
public interface TraceTimeManager {
|
public interface TraceTimeManager {
|
||||||
|
/** The attribute key for controlling the time radix */
|
||||||
|
String KEY_TIME_RADIX = "_time_radix";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new snapshot after the latest
|
* Create a new snapshot after the latest
|
||||||
*
|
*
|
||||||
|
@ -32,6 +36,7 @@ public interface TraceTimeManager {
|
||||||
* Get the snapshot with the given key, optionally creating it
|
* Get the snapshot with the given key, optionally creating it
|
||||||
*
|
*
|
||||||
* @param snap the snapshot key
|
* @param snap the snapshot key
|
||||||
|
* @param createIfAbsent create the snapshot if it's missing
|
||||||
* @return the snapshot or {@code null}
|
* @return the snapshot or {@code null}
|
||||||
*/
|
*/
|
||||||
TraceSnapshot getSnapshot(long snap, boolean createIfAbsent);
|
TraceSnapshot getSnapshot(long snap, boolean createIfAbsent);
|
||||||
|
@ -105,4 +110,24 @@ public interface TraceTimeManager {
|
||||||
* @return the count
|
* @return the count
|
||||||
*/
|
*/
|
||||||
long getSnapshotCount();
|
long getSnapshotCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the radix for displaying and parsing time (snapshots and step counts)
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This only affects the GUI, but storing it in the trace gives the back end a means of
|
||||||
|
* controlling it.
|
||||||
|
*
|
||||||
|
* @param radix the radix
|
||||||
|
*/
|
||||||
|
void setTimeRadix(TimeRadix radix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the radix for displaying and parsing time (snapshots and step counts)
|
||||||
|
*
|
||||||
|
* @see #setTimeRadix(TimeRadix)
|
||||||
|
* @return radix the radix
|
||||||
|
*/
|
||||||
|
TimeRadix getTimeRadix();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
|
|
||||||
public abstract class AbstractStep implements Step {
|
public abstract class AbstractStep implements Step {
|
||||||
protected final long threadKey;
|
protected final long threadKey;
|
||||||
|
@ -34,16 +35,22 @@ public abstract class AbstractStep implements Step {
|
||||||
/**
|
/**
|
||||||
* Return the step portion of {@link #toString()}
|
* Return the step portion of {@link #toString()}
|
||||||
*
|
*
|
||||||
|
* @param radix the radix
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
protected abstract String toStringStepPart();
|
protected abstract String toStringStepPart(TimeRadix radix);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (threadKey == -1) {
|
return toString(TimeRadix.DEFAULT);
|
||||||
return toStringStepPart();
|
|
||||||
}
|
}
|
||||||
return String.format("t%d-", threadKey) + toStringStepPart();
|
|
||||||
|
@Override
|
||||||
|
public String toString(TimeRadix radix) {
|
||||||
|
if (threadKey == -1) {
|
||||||
|
return toStringStepPart(radix);
|
||||||
|
}
|
||||||
|
return String.format("t%d-%s", threadKey, toStringStepPart(radix));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -34,6 +34,7 @@ import ghidra.program.model.lang.Language;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ public class PatchStep implements Step {
|
||||||
|
|
||||||
protected static void generateSleigh(List<String> result, Language language, Address address,
|
protected static void generateSleigh(List<String> result, Language language, Address address,
|
||||||
byte[] data) {
|
byte[] data) {
|
||||||
SemisparseByteArray array = new SemisparseByteArray(); // TODO: Seems heavy-handed
|
SemisparseByteArray array = new SemisparseByteArray(); // Seems heavy-handed
|
||||||
array.putData(address.getOffset(), data);
|
array.putData(address.getOffset(), data);
|
||||||
generateSleigh(result, language, address.getAddressSpace(), array);
|
generateSleigh(result, language, address.getAddressSpace(), array);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +191,7 @@ public class PatchStep implements Step {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PatchStep parse(long threadKey, String stepSpec) {
|
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||||
// TODO: Can I parse and validate the sleigh here?
|
// Would be nice to parse and validate the sleigh here, but need a language.
|
||||||
if (!stepSpec.startsWith("{") || !stepSpec.endsWith("}")) {
|
if (!stepSpec.startsWith("{") || !stepSpec.endsWith("}")) {
|
||||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||||
}
|
}
|
||||||
|
@ -200,7 +201,7 @@ public class PatchStep implements Step {
|
||||||
public PatchStep(long threadKey, String sleigh) {
|
public PatchStep(long threadKey, String sleigh) {
|
||||||
this.threadKey = threadKey;
|
this.threadKey = threadKey;
|
||||||
this.sleigh = Objects.requireNonNull(sleigh);
|
this.sleigh = Objects.requireNonNull(sleigh);
|
||||||
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
this.hashCode = Objects.hash(threadKey, sleigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSleigh(String sleigh) {
|
private void setSleigh(String sleigh) {
|
||||||
|
@ -233,6 +234,11 @@ public class PatchStep implements Step {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
return toString(TimeRadix.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(TimeRadix radix) {
|
||||||
if (threadKey == -1) {
|
if (threadKey == -1) {
|
||||||
return "{" + sleigh + "}";
|
return "{" + sleigh + "}";
|
||||||
}
|
}
|
||||||
|
@ -251,7 +257,6 @@ public class PatchStep implements Step {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNop() {
|
public boolean isNop() {
|
||||||
// TODO: If parsing beforehand, base on number of ops
|
|
||||||
return sleigh.length() == 0;
|
return sleigh.length() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +277,7 @@ public class PatchStep implements Step {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompatible(Step step) {
|
public boolean isCompatible(Step step) {
|
||||||
// TODO: Can we combine ops?
|
// Can we combine ops?
|
||||||
return false; // For now, never combine sleigh steps
|
return false; // For now, never combine sleigh steps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +319,7 @@ public class PatchStep implements Step {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Compare ops, if/when we pre-compile
|
// Compare ops, if/when we pre-compile?
|
||||||
result = CompareResult.unrelated(this.sleigh.compareTo(that.sleigh));
|
result = CompareResult.unrelated(this.sleigh.compareTo(that.sleigh));
|
||||||
if (result != CompareResult.EQUALS) {
|
if (result != CompareResult.EQUALS) {
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -18,14 +18,13 @@ package ghidra.trace.model.time.schedule;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeMachine;
|
import ghidra.pcode.emu.PcodeMachine;
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -33,27 +32,28 @@ import ghidra.util.task.TaskMonitor;
|
||||||
* A sequence of thread steps, each repeated some number of times
|
* A sequence of thread steps, each repeated some number of times
|
||||||
*/
|
*/
|
||||||
public class Sequence implements Comparable<Sequence> {
|
public class Sequence implements Comparable<Sequence> {
|
||||||
public static final String SEP = ";";
|
private static final String SEP = ";";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse (and normalize) a sequence of steps
|
* Parse (and normalize) a sequence of steps
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This takes a semicolon-separated list of steps in the form specified by
|
* This takes a semicolon-separated list of steps in the form specified by
|
||||||
* {@link Step#parse(String)}. Each step may or may not specify a thread, but it's uncommon for
|
* {@link Step#parse(String, TimeRadix)}. Each step may or may not specify a thread, but it's
|
||||||
* any but the first step to omit the thread. The sequence is normalized as it is parsed, so any
|
* uncommon for any but the first step to omit the thread. The sequence is normalized as it is
|
||||||
* step after the first that omits a thread will be combined with the previous step. When the
|
* parsed, so any step after the first that omits a thread will be combined with the previous
|
||||||
* first step applies to the "last thread," it typically means the "event thread" of the source
|
* step. When the first step applies to the "last thread," it typically means the "event thread"
|
||||||
* trace snapshot.
|
* of the source trace snapshot.
|
||||||
*
|
*
|
||||||
* @param seqSpec the string specification of the sequence
|
* @param seqSpec the string specification of the sequence
|
||||||
|
* @param radix the radix
|
||||||
* @return the parsed sequence
|
* @return the parsed sequence
|
||||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
*/
|
*/
|
||||||
public static Sequence parse(String seqSpec) {
|
public static Sequence parse(String seqSpec, TimeRadix radix) {
|
||||||
Sequence result = new Sequence();
|
Sequence result = new Sequence();
|
||||||
for (String stepSpec : seqSpec.split(SEP)) {
|
for (String stepSpec : seqSpec.split(SEP)) {
|
||||||
Step step = Step.parse(stepSpec);
|
Step step = Step.parse(stepSpec, radix);
|
||||||
result.advance(step);
|
result.advance(step);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -109,7 +109,11 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return StringUtils.join(steps, SEP);
|
return toString(TimeRadix.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(TimeRadix radix) {
|
||||||
|
return steps.stream().map(s -> s.toString(radix)).collect(Collectors.joining(SEP));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,17 +16,18 @@
|
||||||
package ghidra.trace.model.time.schedule;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class SkipStep extends AbstractStep {
|
public class SkipStep extends AbstractStep {
|
||||||
|
|
||||||
public static SkipStep parse(long threadKey, String stepSpec) {
|
public static SkipStep parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||||
if (!stepSpec.startsWith("s")) {
|
if (!stepSpec.startsWith("s")) {
|
||||||
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new SkipStep(threadKey, Long.parseLong(stepSpec.substring(1)));
|
return new SkipStep(threadKey, radix.decode(stepSpec.substring(1)));
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'");
|
||||||
|
@ -54,8 +55,8 @@ public class SkipStep extends AbstractStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String toStringStepPart() {
|
protected String toStringStepPart(TimeRadix radix) {
|
||||||
return String.format("s%d", tickCount);
|
return "s" + radix.format(tickCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.pcode.emu.PcodeThread;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -40,21 +41,22 @@ public interface Step extends Comparable<Step> {
|
||||||
* applies to the last thread or the event thread.
|
* applies to the last thread or the event thread.
|
||||||
*
|
*
|
||||||
* @param stepSpec the string specification
|
* @param stepSpec the string specification
|
||||||
|
* @param radix the radix
|
||||||
* @return the parsed step
|
* @return the parsed step
|
||||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
*/
|
*/
|
||||||
static Step parse(String stepSpec) {
|
static Step parse(String stepSpec, TimeRadix radix) {
|
||||||
if ("".equals(stepSpec)) {
|
if ("".equals(stepSpec)) {
|
||||||
return nop();
|
return nop();
|
||||||
}
|
}
|
||||||
String[] parts = stepSpec.split("-");
|
String[] parts = stepSpec.split("-");
|
||||||
if (parts.length == 1) {
|
if (parts.length == 1) {
|
||||||
return parse(-1, parts[0].trim());
|
return parse(-1, parts[0].trim(), radix);
|
||||||
}
|
}
|
||||||
if (parts.length == 2) {
|
if (parts.length == 2) {
|
||||||
String tPart = parts[0].trim();
|
String tPart = parts[0].trim();
|
||||||
if (tPart.startsWith("t")) {
|
if (tPart.startsWith("t")) {
|
||||||
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim());
|
return parse(Long.parseLong(tPart.substring(1)), parts[1].trim(), radix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'");
|
||||||
|
@ -70,23 +72,26 @@ public interface Step extends Comparable<Step> {
|
||||||
*
|
*
|
||||||
* @param threadKey the thread to step, or -1 for the last thread or event thread
|
* @param threadKey the thread to step, or -1 for the last thread or event thread
|
||||||
* @param stepSpec the string specification
|
* @param stepSpec the string specification
|
||||||
|
* @param radix the radix
|
||||||
* @return the parsed step
|
* @return the parsed step
|
||||||
* @throws IllegalArgumentException if the specification is of the wrong form
|
* @throws IllegalArgumentException if the specification is of the wrong form
|
||||||
*/
|
*/
|
||||||
static Step parse(long threadKey, String stepSpec) {
|
static Step parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||||
if (stepSpec.startsWith("s")) {
|
if (stepSpec.startsWith("s")) {
|
||||||
return SkipStep.parse(threadKey, stepSpec);
|
return SkipStep.parse(threadKey, stepSpec, radix);
|
||||||
}
|
}
|
||||||
if (stepSpec.startsWith("{")) {
|
if (stepSpec.startsWith("{")) {
|
||||||
return PatchStep.parse(threadKey, stepSpec);
|
return PatchStep.parse(threadKey, stepSpec);
|
||||||
}
|
}
|
||||||
return TickStep.parse(threadKey, stepSpec);
|
return TickStep.parse(threadKey, stepSpec, radix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TickStep nop() {
|
static TickStep nop() {
|
||||||
return new TickStep(-1, 0);
|
return new TickStep(-1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String toString(TimeRadix radix);
|
||||||
|
|
||||||
StepType getType();
|
StepType getType();
|
||||||
|
|
||||||
default int getTypeOrder() {
|
default int getTypeOrder() {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.trace.model.time.schedule;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -24,9 +25,9 @@ import ghidra.util.task.TaskMonitor;
|
||||||
*/
|
*/
|
||||||
public class TickStep extends AbstractStep {
|
public class TickStep extends AbstractStep {
|
||||||
|
|
||||||
public static TickStep parse(long threadKey, String stepSpec) {
|
public static TickStep parse(long threadKey, String stepSpec, TimeRadix radix) {
|
||||||
try {
|
try {
|
||||||
return new TickStep(threadKey, Long.parseLong(stepSpec));
|
return new TickStep(threadKey, radix.decode(stepSpec));
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("Cannot parse tick step: '" + stepSpec + "'");
|
throw new IllegalArgumentException("Cannot parse tick step: '" + stepSpec + "'");
|
||||||
|
@ -54,8 +55,8 @@ public class TickStep extends AbstractStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String toStringStepPart() {
|
protected String toStringStepPart(TimeRadix radix) {
|
||||||
return Long.toString(tickCount);
|
return radix.format(tickCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,6 +35,62 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
*/
|
*/
|
||||||
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
|
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format for rendering and parsing snaps and step counts
|
||||||
|
*/
|
||||||
|
public enum TimeRadix {
|
||||||
|
/** Use decimal (default) */
|
||||||
|
DEC("dec", 10, "%d"),
|
||||||
|
/** Use upper-case hexadecimal */
|
||||||
|
HEX_UPPER("HEX", 16, "%X"),
|
||||||
|
/** Use lower-case hexadecimal */
|
||||||
|
HEX_LOWER("hex", 16, "%x");
|
||||||
|
|
||||||
|
/** The default radix (decimal) */
|
||||||
|
public static final TimeRadix DEFAULT = DEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the radix specified by the given string
|
||||||
|
*
|
||||||
|
* @param s the name of the specified radix
|
||||||
|
* @return the radix
|
||||||
|
*/
|
||||||
|
public static TimeRadix fromStr(String s) {
|
||||||
|
return switch (s) {
|
||||||
|
case "dec" -> DEC;
|
||||||
|
case "HEX" -> HEX_UPPER;
|
||||||
|
case "hex" -> HEX_LOWER;
|
||||||
|
default -> DEFAULT;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
public final int n;
|
||||||
|
public final String fmt;
|
||||||
|
|
||||||
|
private TimeRadix(String name, int n, String fmt) {
|
||||||
|
this.name = name;
|
||||||
|
this.n = n;
|
||||||
|
this.fmt = fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format(long time) {
|
||||||
|
return fmt.formatted(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long decode(String nm) {
|
||||||
|
if (nm.startsWith("0x") || nm.startsWith("0X") ||
|
||||||
|
nm.startsWith("-0x") || nm.startsWith("-0X")) {
|
||||||
|
return Long.parseLong(nm, 16);
|
||||||
|
}
|
||||||
|
if (nm.startsWith("0n") || nm.startsWith("0N") ||
|
||||||
|
nm.startsWith("-0n") || nm.startsWith("-0N")) {
|
||||||
|
return Long.parseLong(nm, 10);
|
||||||
|
}
|
||||||
|
return Long.parseLong(nm, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies forms of a stepping schedule.
|
* Specifies forms of a stepping schedule.
|
||||||
*
|
*
|
||||||
|
@ -196,14 +252,15 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
* <p>
|
* <p>
|
||||||
* A schedule consists of a snap, a optional {@link Sequence} of thread instruction-level steps,
|
* A schedule consists of a snap, a optional {@link Sequence} of thread instruction-level steps,
|
||||||
* and optional p-code-level steps (pSteps). The form of {@code steps} and {@code pSteps} is
|
* and optional p-code-level steps (pSteps). The form of {@code steps} and {@code pSteps} is
|
||||||
* specified by {@link Sequence#parse(String)}. Each sequence consists of stepping selected
|
* specified by {@link Sequence#parse(String, TimeRadix)}. Each sequence consists of stepping
|
||||||
* threads forward, and/or patching machine state.
|
* selected threads forward, and/or patching machine state.
|
||||||
*
|
*
|
||||||
* @param spec the string specification
|
* @param spec the string specification
|
||||||
* @param source the presumed source of the schedule
|
* @param source the presumed source of the schedule
|
||||||
|
* @param radix the radix
|
||||||
* @return the parsed schedule
|
* @return the parsed schedule
|
||||||
*/
|
*/
|
||||||
public static TraceSchedule parse(String spec, Source source) {
|
public static TraceSchedule parse(String spec, Source source, TimeRadix radix) {
|
||||||
String[] parts = spec.split(":", 2);
|
String[] parts = spec.split(":", 2);
|
||||||
if (parts.length > 2) {
|
if (parts.length > 2) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
|
@ -212,7 +269,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
final Sequence ticks;
|
final Sequence ticks;
|
||||||
final Sequence pTicks;
|
final Sequence pTicks;
|
||||||
try {
|
try {
|
||||||
snap = Long.decode(parts[0]);
|
snap = radix.decode(parts[0]);
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
@ -220,7 +277,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
String[] subs = parts[1].split("\\.");
|
String[] subs = parts[1].split("\\.");
|
||||||
try {
|
try {
|
||||||
ticks = Sequence.parse(subs[0]);
|
ticks = Sequence.parse(subs[0], radix);
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
@ -230,7 +287,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
}
|
}
|
||||||
else if (subs.length == 2) {
|
else if (subs.length == 2) {
|
||||||
try {
|
try {
|
||||||
pTicks = Sequence.parse(subs[1]);
|
pTicks = Sequence.parse(subs[1], radix);
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
throw new IllegalArgumentException(PARSE_ERR_MSG, e);
|
||||||
|
@ -248,13 +305,24 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As in {@link #parse(String, Source)}, but assumed abnormal
|
* As in {@link #parse(String, Source, TimeRadix)}, but assumed abnormal
|
||||||
*
|
*
|
||||||
* @param spec the string specification
|
* @param spec the string specification
|
||||||
|
* @param radix the radix
|
||||||
* @return the parsed schedule
|
* @return the parsed schedule
|
||||||
*/
|
*/
|
||||||
|
public static TraceSchedule parse(String spec, TimeRadix radix) {
|
||||||
|
return parse(spec, Source.INPUT, radix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As in {@link #parse(String, TimeRadix)}, but with the {@link TimeRadix#DEFAULT} radix.
|
||||||
|
*
|
||||||
|
* @param spec the string specification
|
||||||
|
* @return the parse sequence
|
||||||
|
*/
|
||||||
public static TraceSchedule parse(String spec) {
|
public static TraceSchedule parse(String spec) {
|
||||||
return parse(spec, Source.INPUT);
|
return parse(spec, TimeRadix.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Source {
|
public enum Source {
|
||||||
|
@ -318,13 +386,18 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
return toString(TimeRadix.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(TimeRadix radix) {
|
||||||
if (pSteps.isNop()) {
|
if (pSteps.isNop()) {
|
||||||
if (steps.isNop()) {
|
if (steps.isNop()) {
|
||||||
return Long.toString(snap);
|
return radix.format(snap);
|
||||||
}
|
}
|
||||||
return String.format("%d:%s", snap, steps);
|
return String.format("%s:%s", radix.format(snap), steps.toString(radix));
|
||||||
}
|
}
|
||||||
return String.format("%d:%s.%s", snap, steps, pSteps);
|
return String.format("%s:%s.%s", radix.format(snap), steps.toString(radix),
|
||||||
|
pSteps.toString(radix));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,6 +30,7 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||||
import ghidra.test.ToyProgramBuilder;
|
import ghidra.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
@ -161,7 +162,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRewind() {
|
public void testRewind() {
|
||||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
Sequence seq = Sequence.parse("10;t1-20;t2-30", TimeRadix.DEC);
|
||||||
|
|
||||||
assertEquals(0, seq.rewind(5));
|
assertEquals(0, seq.rewind(5));
|
||||||
assertEquals("10;t1-20;t2-25", seq.toString());
|
assertEquals("10;t1-20;t2-25", seq.toString());
|
||||||
|
@ -181,7 +182,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testRewindNegativeErr() {
|
public void testRewindNegativeErr() {
|
||||||
Sequence seq = Sequence.parse("10;t1-20;t2-30");
|
Sequence seq = Sequence.parse("10;t1-20;t2-30", TimeRadix.DEC);
|
||||||
seq.rewind(-1);
|
seq.rewind(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +252,8 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String strRelativize(String fromSpec, String toSpec) {
|
public String strRelativize(String fromSpec, String toSpec) {
|
||||||
Sequence seq = Sequence.parse(toSpec).relativize(Sequence.parse(fromSpec));
|
Sequence seq = Sequence.parse(toSpec, TimeRadix.DEC)
|
||||||
|
.relativize(Sequence.parse(fromSpec, TimeRadix.DEC));
|
||||||
return seq == null ? null : seq.toString();
|
return seq == null ? null : seq.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,4 +505,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
assertFalse(
|
assertFalse(
|
||||||
TraceSchedule.parse("1:1.1;{r0=1}").differsOnlyByPatch(TraceSchedule.parse("1:1.1")));
|
TraceSchedule.parse("1:1.1;{r0=1}").differsOnlyByPatch(TraceSchedule.parse("1:1.1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimeRadix() throws Exception {
|
||||||
|
TraceSchedule schedule = TraceSchedule.parse("A:t10-B.C", TimeRadix.HEX_UPPER);
|
||||||
|
|
||||||
|
assertEquals("10:t10-11.12", schedule.toString(TimeRadix.DEC));
|
||||||
|
assertEquals("A:t10-B.C", schedule.toString(TimeRadix.HEX_UPPER));
|
||||||
|
assertEquals("a:t10-b.c", schedule.toString(TimeRadix.HEX_LOWER));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package generic;
|
package generic;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import generic.Span.Domain;
|
import generic.Span.Domain;
|
||||||
|
|
||||||
|
@ -107,12 +108,12 @@ public interface End<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMinString() {
|
public String toMinString(Function<? super End<Void>, String> nToString) {
|
||||||
return "(-inf";
|
return "(-inf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMaxString() {
|
public String toMaxString(Function<? super End<Void>, String> nToString) {
|
||||||
return "#ERROR-inf)";
|
return "#ERROR-inf)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,12 +139,12 @@ public interface End<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMinString() {
|
public String toMinString(Function<? super End<Void>, String> nToString) {
|
||||||
return "(#ERROR+inf";
|
return "(#ERROR+inf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMaxString() {
|
public String toMaxString(Function<? super End<Void>, String> nToString) {
|
||||||
return "+inf)";
|
return "+inf)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,31 +236,33 @@ public interface End<T> {
|
||||||
/**
|
/**
|
||||||
* An endpoint representing a bound
|
* An endpoint representing a bound
|
||||||
*
|
*
|
||||||
|
* @param val the value of the endpoint
|
||||||
|
* @param epsilon determines whether the endpoint is included
|
||||||
* @param <T> the type of values
|
* @param <T> the type of values
|
||||||
*/
|
*/
|
||||||
record Point<T>(T val, Epsilon epsilon) implements End<T> {
|
record Point<T>(T val, Epsilon epsilon) implements End<T> {
|
||||||
@Override
|
@Override
|
||||||
public String toMinString() {
|
public String toMinString(Function<? super End<T>, String> nToString) {
|
||||||
switch (epsilon) {
|
switch (epsilon) {
|
||||||
case NEGATIVE:
|
case NEGATIVE:
|
||||||
return "(#ERROR" + val;
|
return "(#ERROR" + nToString.apply(this);
|
||||||
case ZERO:
|
case ZERO:
|
||||||
return "[" + val;
|
return "[" + nToString.apply(this);
|
||||||
case POSITIVE:
|
case POSITIVE:
|
||||||
return "(" + val;
|
return "(" + nToString.apply(this);
|
||||||
}
|
}
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMaxString() {
|
public String toMaxString(Function<? super End<T>, String> nToString) {
|
||||||
switch (epsilon) {
|
switch (epsilon) {
|
||||||
case NEGATIVE:
|
case NEGATIVE:
|
||||||
return val + ")";
|
return nToString.apply(this) + ")";
|
||||||
case ZERO:
|
case ZERO:
|
||||||
return val + "]";
|
return nToString.apply(this) + "]";
|
||||||
case POSITIVE:
|
case POSITIVE:
|
||||||
return "#ERROR" + val + ")";
|
return "#ERROR" + nToString.apply(this) + ")";
|
||||||
}
|
}
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
@ -358,13 +361,13 @@ public interface End<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMinString(End<N> min) {
|
public String toMinString(End<N> min, Function<? super End<N>, String> nToString) {
|
||||||
return min.toMinString();
|
return min.toMinString(nToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toMaxString(End<N> max) {
|
public String toMaxString(End<N> max, Function<? super End<N>, String> nToString) {
|
||||||
return max.toMaxString();
|
return max.toMaxString(nToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -394,16 +397,18 @@ public interface End<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Domain#toMinString(Object)
|
* @see Domain#toMinString(Object, Function)
|
||||||
|
* @param nToString the endpoint-to-string function
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
String toMinString();
|
String toMinString(Function<? super End<T>, String> nToString);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Domain#toMaxString(Object)
|
* @see Domain#toMaxString(Object, Function)
|
||||||
|
* @param nToString the endpoint-to-string function
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
String toMaxString();
|
String toMaxString(Function<? super End<T>, String> nToString);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment this endpoint, only by changing the coefficient of epsilon
|
* Increment this endpoint, only by changing the coefficient of epsilon
|
||||||
|
|
|
@ -17,7 +17,8 @@ package ghidra.util.database;
|
||||||
|
|
||||||
import db.Field;
|
import db.Field;
|
||||||
import generic.End;
|
import generic.End;
|
||||||
import generic.End.*;
|
import generic.End.EndDomain;
|
||||||
|
import generic.End.EndSpan;
|
||||||
import generic.Span;
|
import generic.Span;
|
||||||
import ghidra.util.database.DirectedIterator.Direction;
|
import ghidra.util.database.DirectedIterator.Direction;
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ public sealed interface FieldSpan extends EndSpan<Field, FieldSpan> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -163,7 +164,7 @@ public sealed interface FieldSpan extends EndSpan<Field, FieldSpan> {
|
||||||
record Impl(End<Field> min, End<Field> max) implements FieldSpan {
|
record Impl(End<Field> min, End<Field> max) implements FieldSpan {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -165,7 +165,7 @@ public sealed interface KeySpan extends Span<Long, KeySpan> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -180,7 +180,7 @@ public sealed interface KeySpan extends Span<Long, KeySpan> {
|
||||||
record Impl(Long min, Long max) implements KeySpan {
|
record Impl(Long min, Long max) implements KeySpan {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -172,7 +172,7 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -184,7 +184,7 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes
|
||||||
record Impl(Integer min, Integer max) implements IntSpan {
|
record Impl(Integer min, Integer max) implements IntSpan {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -161,9 +161,9 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
|
||||||
boolean hasFocus = data.hasFocus();
|
boolean hasFocus = data.hasFocus();
|
||||||
Settings settings = data.getColumnSettings();
|
Settings settings = data.getColumnSettings();
|
||||||
|
|
||||||
if (value instanceof Number) {
|
if (value instanceof Number n) {
|
||||||
setHorizontalAlignment(SwingConstants.RIGHT);
|
setHorizontalAlignment(SwingConstants.RIGHT);
|
||||||
setText(formatNumber((Number) value, settings));
|
setText(formatNumber(n, settings));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setText(getText(value));
|
setText(getText(value));
|
||||||
|
|
|
@ -17,6 +17,7 @@ package generic;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import generic.ULongSpan.*;
|
import generic.ULongSpan.*;
|
||||||
|
@ -146,30 +147,45 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
* Render the given span as a string
|
* Render the given span as a string
|
||||||
*
|
*
|
||||||
* @param s the span
|
* @param s the span
|
||||||
|
* @param nToString a function to convert n to a string
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
default String toString(S s) {
|
default String toString(S s) {
|
||||||
return s.isEmpty() ? "(empty)" : toMinString(s.min()) + ".." + toMaxString(s.max());
|
return toString(s, this::toString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the given span as a string
|
||||||
|
*
|
||||||
|
* @param s the span
|
||||||
|
* @param nToString a function to convert n to a string
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
default String toString(S s, Function<? super N, String> nToString) {
|
||||||
|
return s.isEmpty() ? "(empty)"
|
||||||
|
: toMinString(s.min(), nToString) + ".." + toMaxString(s.max(), nToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the lower bound of a span
|
* Render the lower bound of a span
|
||||||
*
|
*
|
||||||
* @param min the lower bound
|
* @param min the lower bound
|
||||||
|
* @param nToString a function to convert n to a string
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
default String toMinString(N min) {
|
default String toMinString(N min, Function<? super N, String> nToString) {
|
||||||
return min().equals(min) ? "(-inf" : ("[" + toString(min));
|
return min().equals(min) ? "(-inf" : ("[" + nToString.apply(min));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the upper bound of a span
|
* Render the upper bound of a span
|
||||||
*
|
*
|
||||||
* @param max the upper bound
|
* @param max the upper bound
|
||||||
|
* @param nToString a function to convert n to a string
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
default String toMaxString(N max) {
|
default String toMaxString(N max, Function<? super N, String> nToString) {
|
||||||
return max().equals(max) ? "+inf)" : (toString(max) + "]");
|
return max().equals(max) ? "+inf)" : (nToString.apply(max) + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -558,6 +574,14 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
*/
|
*/
|
||||||
boolean isEmpty();
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this set as a string, using the given endpoint-to-string function
|
||||||
|
*
|
||||||
|
* @param nToString the endpoint-to-string function
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
String toString(Function<? super N, String> nToString);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate the spans in this set
|
* Iterate the spans in this set
|
||||||
*
|
*
|
||||||
|
@ -847,13 +871,16 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString(Function<? super N, String> nToString) {
|
||||||
|
return spanTree.values()
|
||||||
|
.stream()
|
||||||
|
.map(e -> domain.toString(e.getKey(), nToString) + '=' + e.getValue())
|
||||||
|
.collect(Collectors.joining(",", "{", "}"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "{" + spanTree.values()
|
return toString(domain::toString);
|
||||||
.stream()
|
|
||||||
.map(e -> domain.toString(e.getKey()) + '=' + e.getValue())
|
|
||||||
.collect(Collectors.joining(",")) +
|
|
||||||
"}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -863,7 +890,7 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NavigableSet<S> spans() {
|
public NavigableSet<S> spans() {
|
||||||
// TODO: Make this a view?
|
// Make this a view?
|
||||||
return spanTree.values()
|
return spanTree.values()
|
||||||
.stream()
|
.stream()
|
||||||
.map(e -> e.getKey())
|
.map(e -> e.getKey())
|
||||||
|
@ -1007,11 +1034,17 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(Function<? super N, String> nToString) {
|
||||||
|
return map.spans()
|
||||||
|
.stream()
|
||||||
|
.map(s -> domain.toString(s, nToString))
|
||||||
|
.collect(Collectors.joining(",", "[", "]"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return '[' +
|
return toString(domain::toString);
|
||||||
map.spans().stream().map(s -> domain.toString(s)).collect(Collectors.joining(",")) +
|
|
||||||
']';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1087,11 +1120,12 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
|
||||||
/**
|
/**
|
||||||
* Provides a default {@link Object#toString} implementation
|
* Provides a default {@link Object#toString} implementation
|
||||||
*
|
*
|
||||||
|
* @param nToString the endpoint-to-string function
|
||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
default String doToString() {
|
default String toString(Function<? super N, String> nToString) {
|
||||||
return domain().toString((S) this);
|
return domain().toString((S) this, nToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -131,7 +131,7 @@ public interface ULongSpan extends Span<Long, ULongSpan> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -151,7 +151,7 @@ public interface ULongSpan extends Span<Long, ULongSpan> {
|
||||||
record Impl(Long min, Long max) implements ULongSpan {
|
record Impl(Long min, Long max) implements ULongSpan {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return doToString();
|
return toString(DOMAIN::toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue