GP-5523: Allow tool-wide configuration of radix for displaying trace times.

This commit is contained in:
Dan 2025-04-17 18:18:53 +00:00
parent 92e2b6b5d4
commit 004712026b
38 changed files with 751 additions and 246 deletions

View file

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

View file

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

View file

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

View file

@ -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;
@ -83,7 +84,10 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointChanged); listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointChanged);
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);
}); });
@ -118,13 +122,13 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
!(region instanceof TraceObjectMemoryRegion objRegion)) { !(region instanceof TraceObjectMemoryRegion objRegion)) {
return; return;
} }
TraceObject obj = objRegion.getObject(); TraceObject obj = objRegion.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectMemoryRegion.KEY_RANGE, true).forEach(v -> { obj.getOrderedValues(Lifespan.ALL, TraceObjectMemoryRegion.KEY_RANGE, true).forEach(v -> {
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,8 +172,9 @@ 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 =
MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan()); new MemoryBox(currentTrace, "Module " + bpt.getName(v.getMinSnap()),
MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan());
updateList.add(box); updateList.add(box);
}); });
updateLabelDebouncer.contact(null); updateLabelDebouncer.contact(null);
@ -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;

View file

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

View file

@ -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);
@ -179,24 +180,25 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
public Object getColumnValueForRow(MemoryBox box, int columnIndex) { public Object getColumnValueForRow(MemoryBox box, int columnIndex) {
try { try {
switch (columnIndex) { switch (columnIndex) {
case NAME: case NAME:
return box.getId(); return box.getId();
case ASTART: case ASTART:
return box.getRange().getMinAddress(); return box.getRange().getMinAddress();
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;
@ -223,18 +225,18 @@ class MemviewMapModel extends AbstractSortedTableModel<MemoryBox> {
public int compare(MemoryBox b1, MemoryBox b2) { public int compare(MemoryBox b1, MemoryBox b2) {
switch (sortColumn) { switch (sortColumn) {
case NAME: case NAME:
return b1.getId().compareToIgnoreCase(b2.getId()); return b1.getId().compareToIgnoreCase(b2.getId());
case ASTART: case ASTART:
return (int) (b1.getStartAddress() - b2.getStartAddress()); return (int) (b1.getStartAddress() - b2.getStartAddress());
case ASTOP: case ASTOP:
return (int) (b1.getStopAddress() - b2.getStopAddress()); return (int) (b1.getStopAddress() - b2.getStopAddress());
case TSTART: case TSTART:
return (int) (b1.getStartTime() - b2.getStartTime()); return (int) (b1.getStartTime() - b2.getStartTime());
case TSTOP: case TSTOP:
return (int) (b1.getStopTime() - b2.getStopTime()); return (int) (b1.getStopTime() - b2.getStopTime());
default: default:
return 0; return 0;
} }
} }
} }

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -97,27 +97,27 @@ public class MemviewProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, zoomOutTAction); tool.addLocalAction(this, zoomOutTAction);
new ToggleActionBuilder("Toggle Layout", plugin.getName()) // new ToggleActionBuilder("Toggle Layout", plugin.getName()) //
//.menuPath("&Toggle layout") // //.menuPath("&Toggle layout") //
.toolBarIcon(AbstractRefreshAction.ICON) .toolBarIcon(AbstractRefreshAction.ICON)
.helpLocation(new HelpLocation(plugin.getName(), "toggle_layout")) // .helpLocation(new HelpLocation(plugin.getName(), "toggle_layout")) //
.onAction(ctx -> performToggleLayout(ctx)) .onAction(ctx -> performToggleLayout(ctx))
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
new ToggleActionBuilder("Toggle Process Trace", plugin.getName()) // new ToggleActionBuilder("Toggle Process Trace", plugin.getName()) //
//.menuPath("&Toggle layout") // //.menuPath("&Toggle layout") //
.toolBarIcon(DebuggerResources.ICON_SYNC) .toolBarIcon(DebuggerResources.ICON_SYNC)
.helpLocation(new HelpLocation(plugin.getName(), "toggle_process_trace")) // .helpLocation(new HelpLocation(plugin.getName(), "toggle_process_trace")) //
.onAction(ctx -> performToggleTrace(ctx)) .onAction(ctx -> performToggleTrace(ctx))
.selected(false) .selected(false)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
new ToggleActionBuilder("Apply Filter To Panel", plugin.getName()) // new ToggleActionBuilder("Apply Filter To Panel", plugin.getName()) //
//.menuPath("&Toggle layout") // //.menuPath("&Toggle layout") //
.toolBarIcon(DebuggerResources.ICON_FILTER) .toolBarIcon(DebuggerResources.ICON_FILTER)
.helpLocation(new HelpLocation(plugin.getName(), "apply_to_panel")) // .helpLocation(new HelpLocation(plugin.getName(), "apply_to_panel")) //
.onAction(ctx -> performApplyFilterToPanel(ctx)) .onAction(ctx -> performApplyFilterToPanel(ctx))
.selected(true) .selected(true)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
} }
@ -218,7 +218,7 @@ public class MemviewProvider extends ComponentProviderAdapter {
public void goTo(int x, int y) { public void goTo(int x, int y) {
Rectangle bounds = scrollPane.getBounds(); Rectangle bounds = scrollPane.getBounds();
scrollPane.getViewport() scrollPane.getViewport()
.scrollRectToVisible(new Rectangle(x, y, bounds.width, bounds.height)); .scrollRectToVisible(new Rectangle(x, y, bounds.width, bounds.height));
scrollPane.getViewport().doLayout(); scrollPane.getViewport().doLayout();
} }
@ -306,4 +306,10 @@ public class MemviewProvider extends ComponentProviderAdapter {
memviewPanel.reset(); memviewPanel.reset();
}); });
} }
void fireTableDataChanged() {
Swing.runIfSwingOrRunLater(() -> {
memviewTable.fireTableDataChanged();
});
}
} }

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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);
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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());
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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();
} }

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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;
} }

View file

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

View file

@ -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 {
@ -50,8 +50,8 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService {
private TimeTypeOverviewLegendPanel legendPanel; private TimeTypeOverviewLegendPanel legendPanel;
private TimeOverviewColorPlugin plugin; private TimeOverviewColorPlugin plugin;
protected Map<Integer,Long> indexToSnap = new HashMap<>(); protected Map<Integer, Long> indexToSnap = new HashMap<>();
protected Map<Long,Integer> snapToIndex = new HashMap<>(); protected Map<Long, Integer> snapToIndex = new HashMap<>();
protected Lifespan bounds; protected Lifespan bounds;
@Override @Override
@ -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) {
@ -176,7 +177,7 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService {
public Long getSnap(int pixelIndex) { public Long getSnap(int pixelIndex) {
BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount()); BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount());
BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex); BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex);
BigInteger span = BigInteger.valueOf(indexToSnap.size()); BigInteger span = BigInteger.valueOf(indexToSnap.size());
BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight); BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight);
return indexToSnap.get(offset.intValue()); return indexToSnap.get(offset.intValue());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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() {
return toString(TimeRadix.DEFAULT);
}
@Override
public String toString(TimeRadix radix) {
if (threadKey == -1) { if (threadKey == -1) {
return toStringStepPart(); return toStringStepPart(radix);
} }
return String.format("t%d-", threadKey) + toStringStepPart(); return String.format("t%d-%s", threadKey, toStringStepPart(radix));
} }
@Override @Override

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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));

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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