From 004712026b0fdcdfcb67dff8a1f7d70dc979c9ff Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 17 Apr 2025 18:18:53 +0000 Subject: [PATCH] GP-5523: Allow tool-wide configuration of radix for displaying trace times. --- .../api/tracemgr/DebuggerCoordinates.java | 5 +- .../service/tracermi/TraceRmiHandler.java | 5 +- .../DebuggerTimePlugin.html | 9 ++ .../memview/DebuggerMemviewTraceListener.java | 39 +++++--- .../core/debug/gui/memview/MemoryBox.java | 28 +++++- .../debug/gui/memview/MemviewMapModel.java | 78 ++++++++------- .../core/debug/gui/memview/MemviewPanel.java | 11 ++- .../debug/gui/memview/MemviewProvider.java | 46 +++++---- .../core/debug/gui/memview/MemviewTable.java | 4 + .../gui/model/AbstractQueryTableModel.java | 10 ++ .../columns/TracePathLastLifespanColumn.java | 33 +++++-- .../model/columns/TraceValueLifeColumn.java | 24 +++-- .../pcode/DebuggerPcodeStepperProvider.java | 55 ++++++++++- .../gui/time/DebuggerSnapshotTablePanel.java | 65 ++++++++++-- .../debug/gui/time/DebuggerTimeProvider.java | 98 ++++++++++++++++++- .../gui/time/DebuggerTimeSelectionDialog.java | 18 ++-- .../core/debug/gui/time/SnapshotRow.java | 17 ++-- .../TimeTypeOverviewColorService.java | 11 ++- .../gui/trace/DebuggerTraceTabPanel.java | 6 +- .../trace/database/time/DBTraceSnapshot.java | 5 +- .../database/time/DBTraceTimeManager.java | 34 +++++++ .../java/ghidra/trace/model/Lifespan.java | 4 +- .../trace/model/time/TraceTimeManager.java | 25 +++++ .../model/time/schedule/AbstractStep.java | 17 +++- .../trace/model/time/schedule/PatchStep.java | 17 ++-- .../trace/model/time/schedule/Sequence.java | 26 ++--- .../trace/model/time/schedule/SkipStep.java | 9 +- .../trace/model/time/schedule/Step.java | 17 ++-- .../trace/model/time/schedule/TickStep.java | 9 +- .../model/time/schedule/TraceSchedule.java | 95 +++++++++++++++--- .../time/schedule/TraceScheduleTest.java | 17 +++- .../src/main/java/generic/End.java | 51 +++++----- .../java/ghidra/util/database/FieldSpan.java | 11 ++- .../java/ghidra/util/database/KeySpan.java | 8 +- .../table/DemoSpanCellRendererTest.java | 8 +- .../widgets/table/GTableCellRenderer.java | 8 +- .../Emulation/src/main/java/generic/Span.java | 66 ++++++++++--- .../src/main/java/generic/ULongSpan.java | 8 +- 38 files changed, 751 insertions(+), 246 deletions(-) diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java index fb71267384..0224f2de34 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java @@ -39,6 +39,7 @@ import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.Msg; import ghidra.util.NotOwnerException; @@ -712,7 +713,7 @@ public class DebuggerCoordinates { coordState.putLong(KEY_THREAD_KEY, thread.getKey()); } if (time != null) { - coordState.putString(KEY_TIME, time.toString()); + coordState.putString(KEY_TIME, time.toString(TimeRadix.DEC)); } if (frame != null) { coordState.putInt(KEY_FRAME, frame); @@ -785,7 +786,7 @@ public class DebuggerCoordinates { String timeSpec = coordState.getString(KEY_TIME, null); TraceSchedule time; try { - time = TraceSchedule.parse(timeSpec); + time = TraceSchedule.parse(timeSpec, TimeRadix.DEC); } catch (Exception e) { Msg.error(DebuggerCoordinates.class, diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index 3b91dc265f..6783a81851 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -63,6 +63,7 @@ import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; import ghidra.trace.model.target.schema.XmlSchemaContext; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.exception.DuplicateFileException; @@ -1166,8 +1167,8 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { TraceSnapshot snapshot = switch (req.getTimeCase()) { case TIME_NOT_SET -> throw new TraceRmiError("snap or time required"); case SNAP -> open.createSnapshot(req.getSnap().getSnap()); - case SCHEDULE -> open - .createSnapshot(TraceSchedule.parse(req.getSchedule().getSchedule())); + case SCHEDULE -> open.createSnapshot( + TraceSchedule.parse(req.getSchedule().getSchedule(), TimeRadix.DEC)); }; snapshot.setDescription(req.getDescription()); if (!"".equals(req.getDatetime())) { diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html index c8e7812b6c..a8f8beeaaa 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html @@ -101,5 +101,14 @@ emulated state into the trace's scratch space, which comprises all negative snaps. Some time-travel capable back ends may also write into scratch space. When this toggle is enabled, those scratch snapshots are hidden.

+ +

Set Time Radix

+ +

These actions are available when a trace is active. It sets the display radix for snapshot + keys and time schedules throughout the tool. 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.

diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java index 8a1179beb0..4baa034509 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewTraceListener.java @@ -15,7 +15,6 @@ */ package ghidra.app.plugin.core.debug.gui.memview; -import java.awt.Color; import java.util.*; import ghidra.async.AsyncDebouncer; @@ -29,9 +28,11 @@ import ghidra.trace.model.*; import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.memory.*; import ghidra.trace.model.modules.*; -import ghidra.trace.model.stack.*; 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.time.TraceTimeManager; import ghidra.trace.util.TraceEvents; import ghidra.util.Swing; @@ -83,7 +84,10 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointChanged); listenFor(TraceEvents.BYTES_CHANGED, this::bytesChanged); - + + listenFor(TraceEvents.VALUE_CREATED, this::valueCreated); + listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted); + listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored); } @@ -106,7 +110,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { AddressRange rng = rng(defaultSpace, threadId, threadId); TraceObject obj = objThread.getObject(); 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()); updateList.add(box); }); @@ -118,13 +122,13 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { !(region instanceof TraceObjectMemoryRegion objRegion)) { return; } - + TraceObject obj = objRegion.getObject(); obj.getOrderedValues(Lifespan.ALL, TraceObjectMemoryRegion.KEY_RANGE, true).forEach(v -> { if (region.getName(v.getMinSnap()).equals("full memory")) { 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()); updateList.add(box); }); @@ -138,7 +142,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { TraceObject obj = objModule.getObject(); 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()); updateList.add(box); }); @@ -152,7 +156,7 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { TraceObject obj = objSection.getObject(); 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()); updateList.add(box); }); @@ -168,8 +172,9 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { TraceObject obj = objBpt.getObject(); obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true) .forEach(v -> { - MemoryBox box = new MemoryBox("Module " + bpt.getName(v.getMinSnap()), - MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan()); + MemoryBox box = + new MemoryBox(currentTrace, "Module " + bpt.getName(v.getMinSnap()), + MemviewBoxType.BREAKPOINT, v.castValue(), v.getLifespan()); updateList.add(box); }); updateLabelDebouncer.contact(null); @@ -180,12 +185,24 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener { return; } 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); updateList.add(box); 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) { if (!trackTrace) { return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java index f88f76342f..9461b471e7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemoryBox.java @@ -23,9 +23,12 @@ import java.util.Map; import generic.theme.GThemeDefaults.Colors; import ghidra.program.model.address.AddressRange; import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; public class MemoryBox { + protected final Trace trace; protected String id; protected MemviewBoxType type; protected AddressRange range; @@ -46,7 +49,9 @@ public class MemoryBox { 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.type = type; this.range = range; @@ -54,12 +59,13 @@ public class MemoryBox { this.color = color; } - public MemoryBox(String id, MemviewBoxType type, AddressRange range, long tick) { - this(id, type, range, tick, type.getColor()); + public MemoryBox(Trace trace, String id, MemviewBoxType type, AddressRange range, long tick) { + this(trace, id, type, range, tick, type.getColor()); } - public MemoryBox(String id, MemviewBoxType type, AddressRange range, Lifespan trange) { - this(id, type, range, trange.lmin()); + public MemoryBox(Trace trace, String id, MemviewBoxType type, AddressRange range, + Lifespan trange) { + this(trace, id, type, range, trange.lmin()); setEnd(trange.lmax()); } @@ -224,6 +230,18 @@ public class MemoryBox { 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) { if (pos < pixTstart) { return false; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java index 5d68a6ce31..903ab0317e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewMapModel.java @@ -38,7 +38,8 @@ class MemviewMapModel extends AbstractSortedTableModel { private Map memMap = new HashMap<>(); 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) { super(ASTART); @@ -108,9 +109,9 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Convenience method for locating columns by name. Implementation is naive so - * this should be overridden if this method is to be called often. This method - * is not in the TableModel interface and is not used by the JTable. + * Convenience method for locating columns by name. Implementation is naive so this should be + * overridden if this method is to be called often. This method is not in the TableModel + * interface and is not used by the JTable. */ @Override public int findColumn(String columnName) { @@ -142,10 +143,9 @@ class MemviewMapModel extends AbstractSortedTableModel { } /** - * Returns the number of records managed by the data source object. A - * JTable uses this method to determine how many rows it should create - * and display. This method should be quick, as it is call by JTable - * quite frequently. + * Returns the number of records managed by the data source object. A JTable uses this + * method to determine how many rows it should create and display. This method should be quick, + * as it is call by JTable quite frequently. * * @return the number or rows in the model * @see #getColumnCount @@ -165,7 +165,8 @@ class MemviewMapModel extends AbstractSortedTableModel { MemoryBox box = memList.get(rowIndex); try { box.getStart(); - } catch (ConcurrentModificationException e) { + } + catch (ConcurrentModificationException e) { update(); } return memList.get(rowIndex); @@ -179,24 +180,25 @@ class MemviewMapModel extends AbstractSortedTableModel { public Object getColumnValueForRow(MemoryBox box, int columnIndex) { try { switch (columnIndex) { - case NAME: - return box.getId(); - case ASTART: - return box.getRange().getMinAddress(); - case ASTOP: - return box.getRange().getMaxAddress(); - case TSTART: - return Long.toString(box.getStart(), 16); - case TSTOP: - long end = box.getEnd(); - if (end == Long.MAX_VALUE) { - return "+" + '\u221e' + '\u2025'; - } - return Long.toString(end, 16); - default: - return "UNKNOWN"; + case NAME: + return box.getId(); + case ASTART: + return box.getRange().getMinAddress(); + case ASTOP: + return box.getRange().getMaxAddress(); + case TSTART: + return box.formatStart(); + case TSTOP: + long end = box.getEnd(); + if (end == Long.MAX_VALUE) { + return "+" + '\u221e' + '\u2025'; + } + return box.formatEnd(); + default: + return "UNKNOWN"; } - } catch (ConcurrentModificationException e) { + } + catch (ConcurrentModificationException e) { update(); } return null; @@ -223,18 +225,18 @@ class MemviewMapModel extends AbstractSortedTableModel { public int compare(MemoryBox b1, MemoryBox b2) { switch (sortColumn) { - case NAME: - return b1.getId().compareToIgnoreCase(b2.getId()); - case ASTART: - return (int) (b1.getStartAddress() - b2.getStartAddress()); - case ASTOP: - return (int) (b1.getStopAddress() - b2.getStopAddress()); - case TSTART: - return (int) (b1.getStartTime() - b2.getStartTime()); - case TSTOP: - return (int) (b1.getStopTime() - b2.getStopTime()); - default: - return 0; + case NAME: + return b1.getId().compareToIgnoreCase(b2.getId()); + case ASTART: + return (int) (b1.getStartAddress() - b2.getStartAddress()); + case ASTOP: + return (int) (b1.getStopAddress() - b2.getStopAddress()); + case TSTART: + return (int) (b1.getStartTime() - b2.getStartTime()); + case TSTOP: + return (int) (b1.getStopTime() - b2.getStopTime()); + default: + return 0; } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java index 03900d2b26..16a66d7446 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewPanel.java @@ -26,6 +26,7 @@ import generic.theme.GColor; import generic.theme.GThemeDefaults.Colors; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; public class MemviewPanel extends JPanel implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 1L; @@ -484,11 +485,19 @@ public class MemviewPanel extends JPanel implements MouseListener, MouseMotionLi 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) { + TimeRadix radix = getTimeRadix(); String tval = ""; if (0 <= tick && tick < timesArray.length) { Long time = timesArray[(int) tick]; - tval = Long.toString(time, 16); + tval = radix.format(time); } return tval; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java index 5b9d32f130..a580a6153a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewProvider.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -97,27 +97,27 @@ public class MemviewProvider extends ComponentProviderAdapter { tool.addLocalAction(this, zoomOutTAction); new ToggleActionBuilder("Toggle Layout", plugin.getName()) // - //.menuPath("&Toggle layout") // - .toolBarIcon(AbstractRefreshAction.ICON) - .helpLocation(new HelpLocation(plugin.getName(), "toggle_layout")) // - .onAction(ctx -> performToggleLayout(ctx)) - .buildAndInstallLocal(this); + //.menuPath("&Toggle layout") // + .toolBarIcon(AbstractRefreshAction.ICON) + .helpLocation(new HelpLocation(plugin.getName(), "toggle_layout")) // + .onAction(ctx -> performToggleLayout(ctx)) + .buildAndInstallLocal(this); new ToggleActionBuilder("Toggle Process Trace", plugin.getName()) // - //.menuPath("&Toggle layout") // - .toolBarIcon(DebuggerResources.ICON_SYNC) - .helpLocation(new HelpLocation(plugin.getName(), "toggle_process_trace")) // - .onAction(ctx -> performToggleTrace(ctx)) - .selected(false) - .buildAndInstallLocal(this); + //.menuPath("&Toggle layout") // + .toolBarIcon(DebuggerResources.ICON_SYNC) + .helpLocation(new HelpLocation(plugin.getName(), "toggle_process_trace")) // + .onAction(ctx -> performToggleTrace(ctx)) + .selected(false) + .buildAndInstallLocal(this); new ToggleActionBuilder("Apply Filter To Panel", plugin.getName()) // - //.menuPath("&Toggle layout") // - .toolBarIcon(DebuggerResources.ICON_FILTER) - .helpLocation(new HelpLocation(plugin.getName(), "apply_to_panel")) // - .onAction(ctx -> performApplyFilterToPanel(ctx)) - .selected(true) - .buildAndInstallLocal(this); + //.menuPath("&Toggle layout") // + .toolBarIcon(DebuggerResources.ICON_FILTER) + .helpLocation(new HelpLocation(plugin.getName(), "apply_to_panel")) // + .onAction(ctx -> performApplyFilterToPanel(ctx)) + .selected(true) + .buildAndInstallLocal(this); } @@ -218,7 +218,7 @@ public class MemviewProvider extends ComponentProviderAdapter { public void goTo(int x, int y) { Rectangle bounds = scrollPane.getBounds(); scrollPane.getViewport() - .scrollRectToVisible(new Rectangle(x, y, bounds.width, bounds.height)); + .scrollRectToVisible(new Rectangle(x, y, bounds.width, bounds.height)); scrollPane.getViewport().doLayout(); } @@ -306,4 +306,10 @@ public class MemviewProvider extends ComponentProviderAdapter { memviewPanel.reset(); }); } + + void fireTableDataChanged() { + Swing.runIfSwingOrRunLater(() -> { + memviewTable.fireTableDataChanged(); + }); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java index 631cc3b301..065cd2b2bb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memview/MemviewTable.java @@ -175,6 +175,10 @@ public class MemviewTable { } } + void fireTableDataChanged() { + model.fireTableDataChanged(); + } + /* private List generateRows(Collection changed) { List list = new ArrayList<>(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java index fced80404e..3cc9030753 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTableModel.java @@ -28,6 +28,8 @@ import ghidra.framework.plugintool.Plugin; import ghidra.trace.model.*; import ghidra.trace.model.target.TraceObject; 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.util.datastruct.Accumulator; import ghidra.util.exception.CancelledException; @@ -56,12 +58,20 @@ public abstract class AbstractQueryTableModel extends ThreadedTableModel { + extends AbstractDynamicTableColumn { - private final class LastLifespanRenderer extends AbstractGColumnRenderer { + record SpanAndRadix(Lifespan span, TimeRadix radix) implements Comparable { @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 { + @Override + public String getFilterString(SpanAndRadix t, Settings settings) { return t == null ? "" : t.toString(); } @@ -56,17 +70,18 @@ public class TracePathLastLifespanColumn } @Override - public GColumnRenderer getColumnRenderer() { + public GColumnRenderer getColumnRenderer() { return renderer; } @Override - public Lifespan getValue(PathRow rowObject, Settings settings, Trace data, + public SpanAndRadix getValue(PathRow rowObject, Settings settings, Trace data, ServiceProvider serviceProvider) throws IllegalArgumentException { TraceObjectValue lastEntry = rowObject.getPath().getLastEntry(); + TimeRadix radix = data.getTimeManager().getTimeRadix(); if (lastEntry == null) { - return Lifespan.ALL; + return new SpanAndRadix(Lifespan.ALL, radix); } - return lastEntry.getLifespan(); + return new SpanAndRadix(lastEntry.getLifespan(), radix); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifeColumn.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifeColumn.java index ab1a981bad..30ddb822b4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifeColumn.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/columns/TraceValueLifeColumn.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,15 +17,25 @@ package ghidra.app.plugin.core.debug.gui.model.columns; import docking.widgets.table.AbstractDynamicTableColumn; 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.framework.plugintool.ServiceProvider; import ghidra.trace.model.Lifespan.LifeSet; import ghidra.trace.model.Trace; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.table.column.GColumnRenderer; public class TraceValueLifeColumn - extends AbstractDynamicTableColumn { - private final TraceValueColumnRenderer renderer = new TraceValueColumnRenderer<>(); + extends AbstractDynamicTableColumn { + + record SetAndRadix(LifeSet set, TimeRadix radix) { + @Override + public final String toString() { + return set.toString(radix::format); + } + } + + private final TraceValueColumnRenderer renderer = new TraceValueColumnRenderer<>(); @Override public String getColumnName() { @@ -33,13 +43,13 @@ public class TraceValueLifeColumn } @Override - public GColumnRenderer getColumnRenderer() { + public GColumnRenderer getColumnRenderer() { return renderer; } @Override - public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data, + public SetAndRadix getValue(ValueRow rowObject, Settings settings, Trace data, ServiceProvider serviceProvider) throws IllegalArgumentException { - return rowObject.getLife(); + return new SetAndRadix(rowObject.getLife(), data.getTimeManager().getTimeRadix()); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index 24795e3966..c727bc396e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -59,7 +59,13 @@ import ghidra.program.model.listing.Instruction; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; 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.TimeRadix; +import ghidra.trace.util.TraceEvents; import ghidra.util.*; import ghidra.util.table.GhidraTable; 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) { if (color == null) { return ""; @@ -525,6 +550,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { return true; } + private final TraceDomainObjectListener forRadixTraceListener = new ForRadixTraceListener(); + private final DebuggerPcodeStepperPlugin plugin; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; @@ -693,18 +720,36 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { 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) { if (sameCoordinates(current, coordinates)) { current = coordinates; return; } + previous = current; + removeTraceListener(); current = coordinates; + addTraceListener(); doLoadPcodeFrame(); - - setSubTitle(current.getTime().toString()); - + updateSubTitle(); contextChanged(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java index 39cbe246bb..bf51354785 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java @@ -33,10 +33,14 @@ import ghidra.framework.model.DomainObjectEvent; import ghidra.framework.plugintool.PluginTool; 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.TraceTimeManager; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.trace.util.TraceEvents; +import ghidra.util.DateUtils; import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.column.AbstractGColumnRenderer; @@ -46,10 +50,11 @@ public class DebuggerSnapshotTablePanel extends JPanel { implements EnumeratedTableColumn { SNAP("Snap", Long.class, SnapshotRow::getSnap, false), 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), - SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule, false), - DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription, true); + SCHEDULE("Schedule", TraceSchedule.class, SnapshotRow::getSchedule, false), + DESCRIPTION("Description", String.class, SnapshotRow::getDescription, // + SnapshotRow::setDescription, true); private final String header; private final Function getter; @@ -122,6 +127,9 @@ public class DebuggerSnapshotTablePanel extends JPanel { listenFor(TraceEvents.SNAPSHOT_ADDED, this::snapAdded); listenFor(TraceEvents.SNAPSHOT_CHANGED, this::snapChanged); listenFor(TraceEvents.SNAPSHOT_DELETED, this::snapDeleted); + + listenFor(TraceEvents.VALUE_CREATED, this::valueCreated); + listenFor(TraceEvents.VALUE_DELETED, this::valueDeleted); } private void objectRestored() { @@ -132,7 +140,7 @@ public class DebuggerSnapshotTablePanel extends JPanel { if (snapshot.getKey() < 0 && hideScratch) { return; } - SnapshotRow row = new SnapshotRow(currentTrace, snapshot); + SnapshotRow row = new SnapshotRow(snapshot); snapshotTableModel.add(row); } @@ -149,12 +157,50 @@ public class DebuggerSnapshotTablePanel extends JPanel { } 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() { + @Override + protected String formatNumber(Number value, Settings settings) { + return switch (value) { + case 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 -> ""; + case Date date -> DateUtils.formatDateTimestamp(date); + case TraceSchedule schedule -> schedule.toString(getTimeRadix()); + default -> value.toString(); + }; + } + @Override public String getFilterString(Object t, Settings settings) { - return t == null ? "" : t.toString(); + return switch (t) { + case 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 @@ -216,6 +262,11 @@ public class DebuggerSnapshotTablePanel extends JPanel { descCol.setCellRenderer(styleCurrentRenderer); } + protected TimeRadix getTimeRadix() { + return currentTrace == null ? TimeRadix.DEFAULT + : currentTrace.getTimeManager().getTimeRadix(); + } + private void addNewListeners() { if (currentTrace == null) { return; @@ -268,7 +319,7 @@ public class DebuggerSnapshotTablePanel extends JPanel { for (TraceSnapshot snapshot : hideScratch ? manager.getSnapshots(0, true, Long.MAX_VALUE, true) : manager.getAllSnapshots()) { - SnapshotRow row = new SnapshotRow(currentTrace, snapshot); + SnapshotRow row = new SnapshotRow(snapshot); toAdd.add(row); if (current != DebuggerCoordinates.NOWHERE && snapshot.getKey() == current.getViewSnap()) { @@ -289,7 +340,7 @@ public class DebuggerSnapshotTablePanel extends JPanel { Collection sratch = manager.getSnapshots(Long.MIN_VALUE, true, 0, false); snapshotTableModel.addAll(sratch.stream() - .map(s -> new SnapshotRow(currentTrace, s)) + .map(s -> new SnapshotRow(s)) .collect(Collectors.toList())); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java index 5c0a08bae4..4535608fdc 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java @@ -23,9 +23,11 @@ import java.lang.invoke.MethodHandles; import javax.swing.Icon; import javax.swing.JComponent; +import db.Transaction; import docking.ActionContext; import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; 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.AutoServiceConsumed; 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.TraceTimeManager; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; +import ghidra.trace.util.TraceEvents; import ghidra.util.HelpLocation; public class DebuggerTimeProvider extends ComponentProviderAdapter { @@ -54,7 +62,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) .menuPath(DebuggerPluginPackage.NAME, NAME) .menuGroup(GROUP) .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; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; @@ -78,6 +124,9 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { DockingAction actionGoToTime; ToggleDockingAction actionHideScratch; + ToggleDockingAction actionSetRadixDec; + ToggleDockingAction actionSetRadixHexUpper; + ToggleDockingAction actionSetRadixHexLower; private DebuggerSnapActionContext myActionContext; @@ -202,6 +251,21 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { .selected(hideScratch) .onAction(this::activatedHideScratch) .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() { @@ -218,10 +282,42 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { 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) { + removeTraceListener(); current = coordinates; + addTraceListener(); + mainPanel.setTrace(current.getTrace()); 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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java index 6fe9fef187..f5c9fd3d2b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,6 +28,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.framework.plugintool.PluginTool; import ghidra.trace.model.Trace; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.MessageType; import ghidra.util.Msg; @@ -37,6 +38,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { DebuggerSnapshotTablePanel snapshotPanel; JTextField scheduleText; + TimeRadix radix = TimeRadix.DEFAULT; TraceSchedule schedule; JButton tickStep; @@ -56,7 +58,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { if (stepped == null) { return; } - setScheduleText(stepped.toString()); + setScheduleText(stepped.toString(radix)); } catch (Throwable e) { Msg.warn(this, e.getMessage()); @@ -102,7 +104,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { if (schedule.getSnap() == snap.longValue()) { return; } - scheduleText.setText(snap.toString()); + scheduleText.setText(radix.format(snap)); }); scheduleText.getDocument().addDocumentListener(new DocumentListener() { @@ -132,7 +134,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { protected void scheduleTextChanged() { schedule = null; try { - schedule = TraceSchedule.parse(scheduleText.getText()); + schedule = TraceSchedule.parse(scheduleText.getText(), radix); snapshotPanel.setSelectedSnapshot(schedule.getSnap()); schedule.validate(getTrace()); setStatusText(""); @@ -169,6 +171,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { public void close() { super.close(); snapshotPanel.setTrace(null); + radix = TimeRadix.DEFAULT; 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 * * @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 */ public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) { snapshotPanel.setTrace(trace); + radix = trace.getTimeManager().getTimeRadix(); schedule = defaultTime; - scheduleText.setText(defaultTime.toString()); + scheduleText.setText(defaultTime.toString(radix)); tool.showDialog(this); return schedule; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java index 1f69c7cbe9..aac8473150 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/SnapshotRow.java @@ -22,17 +22,14 @@ import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; -import ghidra.util.DateUtils; public class SnapshotRow { - //private static final DateFormat FORMAT = DateFormat.getDateTimeInstance(); - - private final Trace trace; private final TraceSnapshot snapshot; + private final Trace trace; - public SnapshotRow(Trace trace, TraceSnapshot snapshot) { - this.trace = snapshot.getTrace(); + public SnapshotRow(TraceSnapshot snapshot) { this.snapshot = snapshot; + this.trace = snapshot.getTrace(); } public TraceSnapshot getSnapshot() { @@ -51,8 +48,8 @@ public class SnapshotRow { return snapshot.getKey(); } - public String getTimeStamp() { - return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime())); + public Date getTimeStamp() { + return new Date(snapshot.getRealTime()); } public String getEventThreadName() { @@ -60,8 +57,8 @@ public class SnapshotRow { return thread == null ? "" : thread.getName(snapshot.getKey()); } - public String getSchedule() { - return snapshot.getScheduleString(); + public TraceSchedule getSchedule() { + return snapshot.getSchedule(); } public String getDescription() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java index 7b83164e9e..7639ff24a0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/timeoverview/timetype/TimeTypeOverviewColorService.java @@ -27,11 +27,11 @@ import docking.action.builder.ActionBuilder; import generic.theme.GThemeDefaults.Colors; import ghidra.app.plugin.core.debug.gui.timeoverview.*; import ghidra.app.plugin.core.overview.OverviewColorLegendDialog; -import ghidra.app.plugin.core.overview.OverviewColorPlugin; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.*; public class TimeTypeOverviewColorService implements TimeOverviewColorService { @@ -50,8 +50,8 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService { private TimeTypeOverviewLegendPanel legendPanel; private TimeOverviewColorPlugin plugin; - protected Map indexToSnap = new HashMap<>(); - protected Map snapToIndex = new HashMap<>(); + protected Map indexToSnap = new HashMap<>(); + protected Map snapToIndex = new HashMap<>(); protected Lifespan bounds; @Override @@ -82,12 +82,13 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService { if (snap == null) { return ""; } + TimeRadix radix = trace == null ? TimeRadix.DEFAULT : trace.getTimeManager().getTimeRadix(); Set> types = plugin.getTypes(snap); StringBuffer buffer = new StringBuffer(); buffer.append(""); buffer.append(HTMLUtilities.escapeHTML(getName())); buffer.append(" ("); - buffer.append(Long.toHexString(snap)); + buffer.append(radix.format(snap)); buffer.append(")"); buffer.append("\n"); for (Pair pair : types) { @@ -176,7 +177,7 @@ public class TimeTypeOverviewColorService implements TimeOverviewColorService { public Long getSnap(int pixelIndex) { BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount()); BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex); - + BigInteger span = BigInteger.valueOf(indexToSnap.size()); BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight); return indexToSnap.get(offset.intValue()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java index 53fe4f9781..18eda4023e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/trace/DebuggerTraceTabPanel.java @@ -35,6 +35,7 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginEventListener; import ghidra.trace.model.Trace; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.Swing; import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; @@ -123,10 +124,11 @@ public class DebuggerTraceTabPanel extends GTabPanel DebuggerCoordinates current = traceManager == null ? DebuggerCoordinates.NOWHERE : traceManager.getCurrentFor(trace); 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 + " (?)"; } - String schedule = current.getTime().toString(); + TimeRadix radix = trace.getTimeManager().getTimeRadix(); + String schedule = current.getTime().toString(radix); if (schedule.length() > 15) { schedule = "..." + schedule.substring(schedule.length() - 12); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java index 709e754ec4..3cd9599ec2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java @@ -27,6 +27,7 @@ import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.LockHold; import ghidra.util.Msg; import ghidra.util.database.*; @@ -85,7 +86,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot eventThread = manager.threadManager.getThread(threadKey); if (!"".equals(scheduleStr)) { try { - schedule = TraceSchedule.parse(scheduleStr); + schedule = TraceSchedule.parse(scheduleStr, TimeRadix.DEC); } catch (IllegalArgumentException 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) { try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { this.schedule = schedule; - this.scheduleStr = schedule == null ? "" : schedule.toString(); + this.scheduleStr = schedule == null ? "" : schedule.toString(TimeRadix.DEC); update(SCHEDULE_COLUMN); manager.notifySnapshotChanged(this); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java index bb84326967..5ecd2c01a3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java @@ -25,10 +25,14 @@ import db.DBHandle; import ghidra.framework.data.OpenMode; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; +import ghidra.trace.database.target.DBTraceObject; 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.TraceTimeManager; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceEvents; import ghidra.util.LockHold; @@ -181,4 +185,34 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager { 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; + }; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java index de1b3da002..76b256cac1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Lifespan.java @@ -281,7 +281,7 @@ public sealed interface Lifespan extends Span, Iterable { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override @@ -329,7 +329,7 @@ public sealed interface Lifespan extends Span, Iterable { public record Impl(long lmin, long lmax) implements Lifespan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java index af5405bab7..85a3ad2a1a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceTimeManager.java @@ -18,8 +18,12 @@ package ghidra.trace.model.time; import java.util.Collection; import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; public interface TraceTimeManager { + /** The attribute key for controlling the time radix */ + String KEY_TIME_RADIX = "_time_radix"; + /** * Create a new snapshot after the latest * @@ -32,6 +36,7 @@ public interface TraceTimeManager { * Get the snapshot with the given key, optionally creating it * * @param snap the snapshot key + * @param createIfAbsent create the snapshot if it's missing * @return the snapshot or {@code null} */ TraceSnapshot getSnapshot(long snap, boolean createIfAbsent); @@ -105,4 +110,24 @@ public interface TraceTimeManager { * @return the count */ long getSnapshotCount(); + + /** + * Set the radix for displaying and parsing time (snapshots and step counts) + * + *

+ * 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(); + } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java index 4ecab29ae2..1cdb06b59a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/AbstractStep.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule; import java.util.List; import ghidra.program.model.lang.Language; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; public abstract class AbstractStep implements Step { protected final long threadKey; @@ -34,16 +35,22 @@ public abstract class AbstractStep implements Step { /** * Return the step portion of {@link #toString()} * + * @param radix the radix * @return the string */ - protected abstract String toStringStepPart(); + protected abstract String toStringStepPart(TimeRadix radix); @Override public String toString() { + return toString(TimeRadix.DEFAULT); + } + + @Override + public String toString(TimeRadix radix) { 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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java index 18b9e85662..38a9a0b8cb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java @@ -34,6 +34,7 @@ import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -108,7 +109,7 @@ public class PatchStep implements Step { protected static void generateSleigh(List result, Language language, Address address, byte[] data) { - SemisparseByteArray array = new SemisparseByteArray(); // TODO: Seems heavy-handed + SemisparseByteArray array = new SemisparseByteArray(); // Seems heavy-handed array.putData(address.getOffset(), data); generateSleigh(result, language, address.getAddressSpace(), array); } @@ -190,7 +191,7 @@ public class PatchStep implements Step { } 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("}")) { throw new IllegalArgumentException("Cannot parse step: '" + stepSpec + "'"); } @@ -200,7 +201,7 @@ public class PatchStep implements Step { public PatchStep(long threadKey, String sleigh) { this.threadKey = threadKey; 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) { @@ -233,6 +234,11 @@ public class PatchStep implements Step { @Override public String toString() { + return toString(TimeRadix.DEFAULT); + } + + @Override + public String toString(TimeRadix radix) { if (threadKey == -1) { return "{" + sleigh + "}"; } @@ -251,7 +257,6 @@ public class PatchStep implements Step { @Override public boolean isNop() { - // TODO: If parsing beforehand, base on number of ops return sleigh.length() == 0; } @@ -272,7 +277,7 @@ public class PatchStep implements Step { @Override public boolean isCompatible(Step step) { - // TODO: Can we combine ops? + // Can we combine ops? return false; // For now, never combine sleigh steps } @@ -314,7 +319,7 @@ public class PatchStep implements Step { 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)); if (result != CompareResult.EQUALS) { return result; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java index 7de14855ec..74f6c8b4b7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java @@ -18,14 +18,13 @@ package ghidra.trace.model.time.schedule; import java.util.*; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; - import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeThread; import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.exception.CancelledException; 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 */ public class Sequence implements Comparable { - public static final String SEP = ";"; + private static final String SEP = ";"; /** * Parse (and normalize) a sequence of steps * *

* 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 - * any but the first step to omit the thread. The sequence is normalized as it is parsed, so any - * step after the first that omits a thread will be combined with the previous step. When the - * first step applies to the "last thread," it typically means the "event thread" of the source - * trace snapshot. + * {@link Step#parse(String, TimeRadix)}. Each step may or may not specify a thread, but it's + * uncommon for any but the first step to omit the thread. The sequence is normalized as it is + * parsed, so any step after the first that omits a thread will be combined with the previous + * step. When the first step applies to the "last thread," it typically means the "event thread" + * of the source trace snapshot. * * @param seqSpec the string specification of the sequence + * @param radix the radix * @return the parsed sequence * @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(); for (String stepSpec : seqSpec.split(SEP)) { - Step step = Step.parse(stepSpec); + Step step = Step.parse(stepSpec, radix); result.advance(step); } return result; @@ -109,7 +109,11 @@ public class Sequence implements Comparable { @Override 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)); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java index 8702dc3ce9..5b89b516ac 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/SkipStep.java @@ -16,17 +16,18 @@ package ghidra.trace.model.time.schedule; import ghidra.pcode.emu.PcodeThread; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; 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")) { throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'"); } try { - return new SkipStep(threadKey, Long.parseLong(stepSpec.substring(1))); + return new SkipStep(threadKey, radix.decode(stepSpec.substring(1))); } catch (NumberFormatException e) { throw new IllegalArgumentException("Cannot parse skip step: '" + stepSpec + "'"); @@ -54,8 +55,8 @@ public class SkipStep extends AbstractStep { } @Override - protected String toStringStepPart() { - return String.format("s%d", tickCount); + protected String toStringStepPart(TimeRadix radix) { + return "s" + radix.format(tickCount); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java index e4dd0abe03..603b9102db 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Step.java @@ -22,6 +22,7 @@ import ghidra.pcode.emu.PcodeThread; import ghidra.program.model.lang.Language; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -40,21 +41,22 @@ public interface Step extends Comparable { * applies to the last thread or the event thread. * * @param stepSpec the string specification + * @param radix the radix * @return the parsed step * @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)) { return nop(); } String[] parts = stepSpec.split("-"); if (parts.length == 1) { - return parse(-1, parts[0].trim()); + return parse(-1, parts[0].trim(), radix); } if (parts.length == 2) { String tPart = parts[0].trim(); 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 + "'"); @@ -70,23 +72,26 @@ public interface Step extends Comparable { * * @param threadKey the thread to step, or -1 for the last thread or event thread * @param stepSpec the string specification + * @param radix the radix * @return the parsed step * @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")) { - return SkipStep.parse(threadKey, stepSpec); + return SkipStep.parse(threadKey, stepSpec, radix); } if (stepSpec.startsWith("{")) { return PatchStep.parse(threadKey, stepSpec); } - return TickStep.parse(threadKey, stepSpec); + return TickStep.parse(threadKey, stepSpec, radix); } static TickStep nop() { return new TickStep(-1, 0); } + String toString(TimeRadix radix); + StepType getType(); default int getTypeOrder() { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java index f21437535c..bd5333e638 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TickStep.java @@ -16,6 +16,7 @@ package ghidra.trace.model.time.schedule; import ghidra.pcode.emu.PcodeThread; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -24,9 +25,9 @@ import ghidra.util.task.TaskMonitor; */ public class TickStep extends AbstractStep { - public static TickStep parse(long threadKey, String stepSpec) { + public static TickStep parse(long threadKey, String stepSpec, TimeRadix radix) { try { - return new TickStep(threadKey, Long.parseLong(stepSpec)); + return new TickStep(threadKey, radix.decode(stepSpec)); } catch (NumberFormatException e) { throw new IllegalArgumentException("Cannot parse tick step: '" + stepSpec + "'"); @@ -54,8 +55,8 @@ public class TickStep extends AbstractStep { } @Override - protected String toStringStepPart() { - return Long.toString(tickCount); + protected String toStringStepPart(TimeRadix radix) { + return radix.format(tickCount); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 45e525f073..0bb79e511d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -35,6 +35,62 @@ public class TraceSchedule implements Comparable { */ 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. * @@ -196,14 +252,15 @@ public class TraceSchedule implements Comparable { *

* 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 - * specified by {@link Sequence#parse(String)}. Each sequence consists of stepping selected - * threads forward, and/or patching machine state. + * specified by {@link Sequence#parse(String, TimeRadix)}. Each sequence consists of stepping + * selected threads forward, and/or patching machine state. * * @param spec the string specification * @param source the presumed source of the schedule + * @param radix the radix * @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); if (parts.length > 2) { throw new AssertionError(); @@ -212,7 +269,7 @@ public class TraceSchedule implements Comparable { final Sequence ticks; final Sequence pTicks; try { - snap = Long.decode(parts[0]); + snap = radix.decode(parts[0]); } catch (NumberFormatException e) { throw new IllegalArgumentException(PARSE_ERR_MSG, e); @@ -220,7 +277,7 @@ public class TraceSchedule implements Comparable { if (parts.length > 1) { String[] subs = parts[1].split("\\."); try { - ticks = Sequence.parse(subs[0]); + ticks = Sequence.parse(subs[0], radix); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(PARSE_ERR_MSG, e); @@ -230,7 +287,7 @@ public class TraceSchedule implements Comparable { } else if (subs.length == 2) { try { - pTicks = Sequence.parse(subs[1]); + pTicks = Sequence.parse(subs[1], radix); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(PARSE_ERR_MSG, e); @@ -248,13 +305,24 @@ public class TraceSchedule implements Comparable { } /** - * 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 radix the radix * @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) { - return parse(spec, Source.INPUT); + return parse(spec, TimeRadix.DEFAULT); } public enum Source { @@ -318,13 +386,18 @@ public class TraceSchedule implements Comparable { @Override public String toString() { + return toString(TimeRadix.DEFAULT); + } + + public String toString(TimeRadix radix) { if (pSteps.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)); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java index 721308b212..5f895ea74f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java @@ -30,6 +30,7 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix; import ghidra.util.task.TaskMonitor; public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { @@ -161,7 +162,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { @Test 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("10;t1-20;t2-25", seq.toString()); @@ -181,7 +182,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { @Test(expected = IllegalArgumentException.class) 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); } @@ -251,7 +252,8 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { } 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(); } @@ -503,4 +505,13 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { assertFalse( 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)); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/generic/End.java b/Ghidra/Debug/ProposedUtils/src/main/java/generic/End.java index aff6dd8c09..dd230faf91 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/generic/End.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/generic/End.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,6 +16,7 @@ package generic; import java.util.Comparator; +import java.util.function.Function; import generic.Span.Domain; @@ -107,12 +108,12 @@ public interface End { } @Override - public String toMinString() { + public String toMinString(Function, String> nToString) { return "(-inf"; } @Override - public String toMaxString() { + public String toMaxString(Function, String> nToString) { return "#ERROR-inf)"; } @@ -138,12 +139,12 @@ public interface End { } @Override - public String toMinString() { + public String toMinString(Function, String> nToString) { return "(#ERROR+inf"; } @Override - public String toMaxString() { + public String toMaxString(Function, String> nToString) { return "+inf)"; } @@ -235,31 +236,33 @@ public interface End { /** * An endpoint representing a bound * + * @param val the value of the endpoint + * @param epsilon determines whether the endpoint is included * @param the type of values */ - record Point (T val, Epsilon epsilon) implements End { + record Point(T val, Epsilon epsilon) implements End { @Override - public String toMinString() { + public String toMinString(Function, String> nToString) { switch (epsilon) { case NEGATIVE: - return "(#ERROR" + val; + return "(#ERROR" + nToString.apply(this); case ZERO: - return "[" + val; + return "[" + nToString.apply(this); case POSITIVE: - return "(" + val; + return "(" + nToString.apply(this); } throw new AssertionError(); } @Override - public String toMaxString() { + public String toMaxString(Function, String> nToString) { switch (epsilon) { case NEGATIVE: - return val + ")"; + return nToString.apply(this) + ")"; case ZERO: - return val + "]"; + return nToString.apply(this) + "]"; case POSITIVE: - return "#ERROR" + val + ")"; + return "#ERROR" + nToString.apply(this) + ")"; } throw new AssertionError(); } @@ -358,13 +361,13 @@ public interface End { } @Override - public String toMinString(End min) { - return min.toMinString(); + public String toMinString(End min, Function, String> nToString) { + return min.toMinString(nToString); } @Override - public String toMaxString(End max) { - return max.toMaxString(); + public String toMaxString(End max, Function, String> nToString) { + return max.toMaxString(nToString); } @Override @@ -394,16 +397,18 @@ public interface End { } /** - * @see Domain#toMinString(Object) + * @see Domain#toMinString(Object, Function) + * @param nToString the endpoint-to-string function * @return the string */ - String toMinString(); + String toMinString(Function, String> nToString); /** - * @see Domain#toMaxString(Object) + * @see Domain#toMaxString(Object, Function) + * @param nToString the endpoint-to-string function * @return the string */ - String toMaxString(); + String toMaxString(Function, String> nToString); /** * Increment this endpoint, only by changing the coefficient of epsilon diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/FieldSpan.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/FieldSpan.java index 483a80c35c..dac5a1d05f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/FieldSpan.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/FieldSpan.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,8 @@ package ghidra.util.database; import db.Field; import generic.End; -import generic.End.*; +import generic.End.EndDomain; +import generic.End.EndSpan; import generic.Span; import ghidra.util.database.DirectedIterator.Direction; @@ -148,7 +149,7 @@ public sealed interface FieldSpan extends EndSpan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override @@ -163,7 +164,7 @@ public sealed interface FieldSpan extends EndSpan { record Impl(End min, End max) implements FieldSpan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/KeySpan.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/KeySpan.java index f2d7ace57b..2656eb8c20 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/KeySpan.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/KeySpan.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -165,7 +165,7 @@ public sealed interface KeySpan extends Span { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override @@ -180,7 +180,7 @@ public sealed interface KeySpan extends Span { record Impl(Long min, Long max) implements KeySpan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java index 78c6e90868..f062ac51fa 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoSpanCellRendererTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -172,7 +172,7 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override @@ -184,7 +184,7 @@ public class DemoSpanCellRendererTest extends AbstractGhidraHeadedIntegrationTes record Impl(Integer min, Integer max) implements IntSpan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java index 01df7ad851..3103af5c63 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -161,9 +161,9 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe boolean hasFocus = data.hasFocus(); Settings settings = data.getColumnSettings(); - if (value instanceof Number) { + if (value instanceof Number n) { setHorizontalAlignment(SwingConstants.RIGHT); - setText(formatNumber((Number) value, settings)); + setText(formatNumber(n, settings)); } else { setText(getText(value)); diff --git a/Ghidra/Framework/Emulation/src/main/java/generic/Span.java b/Ghidra/Framework/Emulation/src/main/java/generic/Span.java index 2be8cc7014..2f9b112161 100644 --- a/Ghidra/Framework/Emulation/src/main/java/generic/Span.java +++ b/Ghidra/Framework/Emulation/src/main/java/generic/Span.java @@ -17,6 +17,7 @@ package generic; import java.util.*; import java.util.Map.Entry; +import java.util.function.Function; import java.util.stream.Collectors; import generic.ULongSpan.*; @@ -146,30 +147,45 @@ public interface Span> extends Comparable { * 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) { - 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 nToString) { + return s.isEmpty() ? "(empty)" + : toMinString(s.min(), nToString) + ".." + toMaxString(s.max(), nToString); } /** * Render the lower bound of a span * * @param min the lower bound + * @param nToString a function to convert n to a string * @return the string */ - default String toMinString(N min) { - return min().equals(min) ? "(-inf" : ("[" + toString(min)); + default String toMinString(N min, Function nToString) { + return min().equals(min) ? "(-inf" : ("[" + nToString.apply(min)); } /** * Render the upper bound of a span * * @param max the upper bound + * @param nToString a function to convert n to a string * @return the string */ - default String toMaxString(N max) { - return max().equals(max) ? "+inf)" : (toString(max) + "]"); + default String toMaxString(N max, Function nToString) { + return max().equals(max) ? "+inf)" : (nToString.apply(max) + "]"); } /** @@ -558,6 +574,14 @@ public interface Span> extends Comparable { */ 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 nToString); + /** * Iterate the spans in this set * @@ -847,13 +871,16 @@ public interface Span> extends Comparable { return true; } + public String toString(Function nToString) { + return spanTree.values() + .stream() + .map(e -> domain.toString(e.getKey(), nToString) + '=' + e.getValue()) + .collect(Collectors.joining(",", "{", "}")); + } + @Override public String toString() { - return "{" + spanTree.values() - .stream() - .map(e -> domain.toString(e.getKey()) + '=' + e.getValue()) - .collect(Collectors.joining(",")) + - "}"; + return toString(domain::toString); } @Override @@ -863,7 +890,7 @@ public interface Span> extends Comparable { @Override public NavigableSet spans() { - // TODO: Make this a view? + // Make this a view? return spanTree.values() .stream() .map(e -> e.getKey()) @@ -1007,11 +1034,17 @@ public interface Span> extends Comparable { return true; } + @Override + public String toString(Function nToString) { + return map.spans() + .stream() + .map(s -> domain.toString(s, nToString)) + .collect(Collectors.joining(",", "[", "]")); + } + @Override public String toString() { - return '[' + - map.spans().stream().map(s -> domain.toString(s)).collect(Collectors.joining(",")) + - ']'; + return toString(domain::toString); } /** @@ -1087,11 +1120,12 @@ public interface Span> extends Comparable { /** * Provides a default {@link Object#toString} implementation * + * @param nToString the endpoint-to-string function * @return the string */ @SuppressWarnings("unchecked") - default String doToString() { - return domain().toString((S) this); + default String toString(Function nToString) { + return domain().toString((S) this, nToString); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/generic/ULongSpan.java b/Ghidra/Framework/Emulation/src/main/java/generic/ULongSpan.java index af565b72f7..42fe4b0c34 100644 --- a/Ghidra/Framework/Emulation/src/main/java/generic/ULongSpan.java +++ b/Ghidra/Framework/Emulation/src/main/java/generic/ULongSpan.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -131,7 +131,7 @@ public interface ULongSpan extends Span { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override @@ -151,7 +151,7 @@ public interface ULongSpan extends Span { record Impl(Long min, Long max) implements ULongSpan { @Override public String toString() { - return doToString(); + return toString(DOMAIN::toString); } @Override