diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java
new file mode 100644
index 0000000000..54adfe4b36
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java
@@ -0,0 +1,378 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.debug.gui.modules;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.*;
+import java.math.BigInteger;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+
+import com.google.common.collect.Range;
+
+import docking.DialogComponentProvider;
+import docking.widgets.model.GAddressRangeField;
+import docking.widgets.model.GLifespanField;
+import ghidra.app.services.DebuggerStaticMappingService;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramLocation;
+import ghidra.trace.model.*;
+import ghidra.trace.model.modules.TraceConflictedMappingException;
+import ghidra.util.MathUtilities;
+import ghidra.util.database.UndoableTransaction;
+import ghidra.util.layout.PairLayout;
+
+public class DebuggerAddMappingDialog extends DialogComponentProvider {
+ private static final String HEX_BIT64 = "0x" + BigInteger.ONE.shiftLeft(64).toString(16);
+
+ private DebuggerStaticMappingService mappingService;
+
+ private Program program;
+ private Trace trace;
+
+ private final JLabel labelProg = new JLabel();
+ private final GAddressRangeField fieldProgRange = new GAddressRangeField();
+ private final JLabel labelTrace = new JLabel();
+ private final GAddressRangeField fieldTraceRange = new GAddressRangeField();
+ private final JTextField fieldLength = new JTextField();
+ private final GLifespanField fieldSpan = new GLifespanField();
+
+ public DebuggerAddMappingDialog() {
+ super("Add Static Mapping", false, false, true, false);
+
+ populateComponents();
+ }
+
+ protected static void rigFocusAndEnter(Component c, Runnable runnable) {
+ c.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ runnable.run();
+ }
+ });
+ c.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ runnable.run();
+ }
+ }
+ });
+ }
+
+ protected void populateComponents() {
+ JPanel panel = new JPanel(new PairLayout(5, 5));
+
+ panel.setBorder(new EmptyBorder(10, 10, 10, 10));
+
+ panel.add(new JLabel("Program: "));
+ panel.add(labelProg);
+
+ panel.add(new JLabel("Static Range: "));
+ panel.add(fieldProgRange);
+
+ panel.add(new JLabel("Trace: "));
+ panel.add(labelTrace);
+
+ panel.add(new JLabel("Dynamic Range: "));
+ panel.add(fieldTraceRange);
+
+ panel.add(new JLabel("Length: "));
+ fieldLength.setFont(Font.decode("monospaced"));
+ panel.add(fieldLength);
+
+ panel.add(new JLabel("Lifespan: "));
+ panel.add(fieldSpan);
+
+ rigFocusAndEnter(fieldProgRange, this::progRangeChanged);
+ rigFocusAndEnter(fieldTraceRange, this::traceRangeChanged);
+ rigFocusAndEnter(fieldLength, this::lengthChanged);
+ rigFocusAndEnter(fieldSpan, this::spanChanged);
+
+ addWorkPanel(panel);
+
+ addApplyButton();
+ addDismissButton();
+ setDefaultButton(null);
+ }
+
+ public void setMappingService(DebuggerStaticMappingService mappingService) {
+ this.mappingService = mappingService;
+ }
+
+ protected static void revalidateByLength(GAddressRangeField adjusted,
+ GAddressRangeField other) {
+ AddressRange adjRange = adjusted.getRange();
+ if (adjRange == null) {
+ return;
+ }
+ long lengthMinus1 = adjRange.getMaxAddress().subtract(adjRange.getMinAddress());
+ AddressRange otherRange = other.getRange();
+ if (otherRange == null) {
+ return;
+ }
+ long maxLengthMinus1 =
+ otherRange.getAddressSpace().getMaxAddress().subtract(otherRange.getMinAddress());
+ if (Long.compareUnsigned(lengthMinus1, maxLengthMinus1) > 0) {
+ adjusted.setRange(range(adjRange.getMinAddress(), maxLengthMinus1));
+ }
+ }
+
+ protected void revalidateProgRange() {
+ /**
+ * Not sure there's much to do here wrt/ the program. Might be nice to warn (but not
+ * prohibit) when range is not in memory. NB: A range spanning all blocks will likely
+ * contain some dead space.
+ */
+ revalidateByLength(fieldProgRange, fieldTraceRange);
+ }
+
+ protected void revalidateTraceRange() {
+ // Similarly nothing to really do wrt/ the trace.
+ revalidateByLength(fieldTraceRange, fieldProgRange);
+ }
+
+ protected static long lengthMin(long a, long b) {
+ if (a == 0) {
+ return b;
+ }
+ if (b == 0) {
+ return a;
+ }
+ return MathUtilities.unsignedMin(a, b);
+ }
+
+ protected static long revalidateLengthByRange(AddressRange range, long length) {
+ long maxLength =
+ range.getAddressSpace().getMaxAddress().subtract(range.getMinAddress()) + 1;
+ return lengthMin(length, maxLength);
+ }
+
+ protected void setFieldLength(long length) {
+ if (length == 0) {
+ fieldLength.setText(HEX_BIT64);
+ }
+ else {
+ fieldLength.setText("0x" + Long.toHexString(length));
+ }
+ }
+
+ public long getLength() {
+ return parseLength(fieldLength.getText(), 1);
+ }
+
+ protected void revalidateLength() {
+ long length;
+ if (fieldLength.getText().trim().startsWith("-")) {
+ length = 1;
+ }
+ else {
+ length = getLength();
+ }
+ length = revalidateLengthByRange(fieldProgRange.getRange(), length);
+ length = revalidateLengthByRange(fieldTraceRange.getRange(), length);
+ setFieldLength(length);
+ }
+
+ protected void revalidateSpan() {
+ // Yeah, nothing to do
+ }
+
+ protected static AddressRange range(Address min, long lengthMinus1) {
+ return new AddressRangeImpl(min, min.addWrap(lengthMinus1));
+ }
+
+ protected void adjustLengthToProgRange() {
+ long length = fieldProgRange.getRange().getLength();
+ setFieldLength(length);
+ }
+
+ protected void adjustLengthToTraceRange() {
+ long length = fieldTraceRange.getRange().getLength();
+ setFieldLength(length);
+ }
+
+ protected void adjustRangeToLength(GAddressRangeField field) {
+ AddressRange range = field.getRange();
+ if (range == null) {
+ return;
+ }
+ Address min = range.getMinAddress();
+ field.setRange(range(min, getLength() - 1));
+ }
+
+ protected void adjustProgRangeToLength() {
+ adjustRangeToLength(fieldProgRange);
+ }
+
+ protected void adjustTraceRangeToLength() {
+ adjustRangeToLength(fieldTraceRange);
+ }
+
+ protected void progRangeChanged() {
+ revalidateProgRange();
+ adjustLengthToProgRange();
+ adjustTraceRangeToLength();
+ }
+
+ protected void traceRangeChanged() {
+ revalidateTraceRange();
+ adjustLengthToTraceRange();
+ adjustProgRangeToLength();
+ }
+
+ protected void lengthChanged() {
+ revalidateLength();
+ adjustProgRangeToLength();
+ adjustTraceRangeToLength();
+ }
+
+ protected void spanChanged() {
+ revalidateSpan();
+ }
+
+ /**
+ * Parses a value from 1 to 1<<64. Any value outside the range is "clipped" into the range.
+ *
+ *
+ * Note that a returned value of 0 indicates 2 to the power 64, which is just 1 too high to fit
+ * into a 64-bit long.
+ *
+ * @param text the text to parse
+ * @param defaultVal the default value should parsing fail altogether
+ * @return the length, where 0 indicates {@code 1 << 64}.
+ */
+ protected static long parseLength(String text, long defaultVal) {
+ text = text.trim();
+ String post;
+ int radix;
+ if (text.startsWith("-")) {
+ return 0;
+ }
+ if (text.startsWith("0x")) {
+ post = text.substring(2);
+ radix = 16;
+ }
+ else {
+ post = text;
+ radix = 10;
+ }
+ BigInteger bi;
+ try {
+ bi = new BigInteger(post, radix);
+ }
+ catch (NumberFormatException e) {
+ return defaultVal;
+ }
+ if (bi.equals(BigInteger.ZERO)) {
+ return 1;
+ }
+ if (bi.bitLength() > 64) {
+ return 0; // indicates 2**64, the max length
+ }
+ return bi.longValue(); // Do not use exact. It checks bitLength again, and considers sign.
+ }
+
+ @Override
+ protected void applyCallback() {
+ TraceLocation from = new DefaultTraceLocation(trace, null, fieldSpan.getLifespan(),
+ fieldTraceRange.getRange().getMinAddress());
+ ProgramLocation to = new ProgramLocation(program,
+ fieldProgRange.getRange().getMinAddress());
+
+ try (UndoableTransaction tid =
+ UndoableTransaction.start(trace, "Add Static Mapping", false)) {
+ mappingService.addMapping(from, to, getLength(), true);
+ tid.commit();
+ }
+ catch (TraceConflictedMappingException e) {
+ throw new AssertionError(e); // I said truncateExisting
+ }
+ }
+
+ /**
+ * Set the values of the fields
+ *
+ * @param program the program
+ * @param trace the trace
+ * @param progStart the starting static address
+ * @param traceStart the starting dynamic address
+ * @param length the length (0 indicates the entire 64-bit range)
+ * @param lifespan the lifespan
+ * @throws AddressOverflowException if the length is too large for either space
+ */
+ public void setValues(Program program, Trace trace, Address progStart,
+ Address traceStart, long length, Range lifespan) throws AddressOverflowException {
+ // NB. This dialog will not validate these. The caller is responsible.
+ this.program = program;
+ this.trace = trace;
+ this.fieldProgRange.setAddressFactory(program.getAddressFactory());
+ this.fieldProgRange.setRange(range(progStart, length - 1));
+ this.fieldTraceRange.setAddressFactory(trace.getBaseAddressFactory());
+ this.fieldTraceRange.setRange(range(traceStart, length - 1));
+ setFieldLength(length);
+ this.fieldSpan.setLifespan(lifespan);
+ }
+
+ protected void setEnabled(boolean enabled) {
+ applyButton.setEnabled(enabled);
+ fieldProgRange.setEnabled(enabled);
+ fieldTraceRange.setEnabled(enabled);
+ fieldLength.setEnabled(enabled);
+ fieldSpan.setEnabled(enabled);
+ }
+
+ public void setTrace(Trace trace) {
+ this.trace = trace;
+ if (trace != null) {
+ labelTrace.setText(trace.getName());
+ fieldTraceRange.setAddressFactory(trace.getBaseAddressFactory());
+ }
+ else {
+ labelTrace.setText("[No Trace]");
+ fieldTraceRange.setAddressFactory(null);
+ }
+ if (program != null && trace != null) {
+ traceRangeChanged();
+ setEnabled(true);
+ }
+ else {
+ setEnabled(false);
+ }
+ }
+
+ public void setProgram(Program program) {
+ this.program = program;
+ if (program != null) {
+ labelProg.setText(program.getName());
+ fieldProgRange.setAddressFactory(program.getAddressFactory());
+ }
+ else {
+ labelProg.setText("[No Program]");
+ fieldProgRange.setAddressFactory(null);
+ }
+ if (program != null && trace != null) {
+ progRangeChanged();
+ setEnabled(true);
+ }
+ else {
+ setEnabled(false);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingPlugin.java
index 0cbc172652..aa1b1ae218 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingPlugin.java
@@ -15,35 +15,30 @@
*/
package ghidra.app.plugin.core.debug.gui.modules;
-import ghidra.app.events.ProgramLocationPluginEvent;
-import ghidra.app.events.ProgramSelectionPluginEvent;
+import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
-import ghidra.app.plugin.core.debug.event.*;
+import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
-@PluginInfo( //
- shortDescription = "Debugger static mapping manager", //
- description = "GUI to manage static mappings", //
- category = PluginCategoryNames.DEBUGGER, //
- packageName = DebuggerPluginPackage.NAME, //
- status = PluginStatus.RELEASED, //
- eventsConsumed = {
- TraceActivatedPluginEvent.class, //
- ProgramLocationPluginEvent.class, //
- ProgramSelectionPluginEvent.class, //
- TraceLocationPluginEvent.class, //
- TraceSelectionPluginEvent.class //
- }, //
- servicesRequired = { //
- DebuggerStaticMappingService.class, //
- DebuggerTraceManagerService.class, //
- } //
-)
+@PluginInfo(
+ shortDescription = "Debugger static mapping manager",
+ description = "GUI to manage static mappings",
+ category = PluginCategoryNames.DEBUGGER,
+ packageName = DebuggerPluginPackage.NAME,
+ status = PluginStatus.RELEASED,
+ eventsConsumed = {
+ TraceActivatedPluginEvent.class,
+ ProgramActivatedPluginEvent.class,
+ },
+ servicesRequired = {
+ DebuggerStaticMappingService.class,
+ DebuggerTraceManagerService.class,
+ })
public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
protected DebuggerStaticMappingProvider provider;
@@ -70,17 +65,9 @@ public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.setTrace(ev.getActiveCoordinates().getTrace());
}
- if (event instanceof ProgramLocationPluginEvent) {
- provider.contextChanged();
- }
- if (event instanceof ProgramSelectionPluginEvent) {
- provider.contextChanged();
- }
- if (event instanceof TraceLocationPluginEvent) {
- provider.contextChanged();
- }
- if (event instanceof TraceSelectionPluginEvent) {
- provider.contextChanged();
+ if (event instanceof ProgramActivatedPluginEvent) {
+ ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
+ provider.setProgram(ev.getActiveProgram());
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java
index 5122d1f48f..732c218f7b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java
@@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.modules;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
+import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.function.Function;
@@ -41,15 +42,18 @@ import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
-import ghidra.trace.model.*;
+import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceStaticMappingChangeType;
-import ghidra.trace.model.modules.*;
+import ghidra.trace.model.TraceDomainObjectListener;
+import ghidra.trace.model.modules.TraceStaticMapping;
+import ghidra.trace.model.modules.TraceStaticMappingManager;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.MathUtilities;
+import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTableFilterPanel;
@@ -60,7 +64,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
DYNAMIC_ADDRESS("Dynamic Address", Address.class, StaticMappingRow::getTraceAddress),
STATIC_URL("Static Program", URL.class, StaticMappingRow::getStaticProgramURL),
STATIC_ADDRESS("Static Address", String.class, StaticMappingRow::getStaticAddress),
- LENGTH("Length", Long.class, StaticMappingRow::getLength),
+ LENGTH("Length", BigInteger.class, StaticMappingRow::getBigLength),
SHIFT("Shift", Long.class, StaticMappingRow::getShift),
LIFESPAN("Lifespan", Range.class, StaticMappingRow::getLifespan);
@@ -113,6 +117,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
private final DebuggerStaticMappingPlugin plugin;
+ private final DebuggerAddMappingDialog addMappingDialog;
+
@AutoServiceConsumed
private DebuggerStaticMappingService mappingService;
// TODO: Use events to track selections? This can only work with the main listings.
@@ -146,6 +152,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null);
this.plugin = plugin;
+ this.addMappingDialog = new DebuggerAddMappingDialog();
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
setIcon(DebuggerResources.ICON_PROVIDER_MAPPINGS);
@@ -157,6 +164,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
createActions();
}
+ @AutoServiceConsumed
+ private void setMappingService(DebuggerStaticMappingService mappingService) {
+ addMappingDialog.setMappingService(mappingService);
+ }
+
@Override
public void addLocalAction(DockingActionIf action) {
super.addLocalAction(action);
@@ -215,7 +227,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
statAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lengthCol = columnModel.getColumn(StaticMappingTableColumns.LENGTH.ordinal());
// TODO: Get user column settings working. Still, should default to Hex
- lengthCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX);
+ lengthCol.setCellRenderer(CustomToStringCellRenderer.MONO_BIG_HEX);
TableColumn shiftCol = columnModel.getColumn(StaticMappingTableColumns.SHIFT.ordinal());
shiftCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX);
}
@@ -223,7 +235,6 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
protected void createActions() {
actionAdd = AddAction.builder(plugin)
.description("Add Mapping from Listing Selections")
- .enabledWhen(this::haveMappableSelections)
.onAction(this::activatedAdd)
.buildAndInstallLocal(this);
actionRemove = RemoveAction.builder(plugin)
@@ -240,38 +251,10 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
contextChanged();
}
- private boolean haveMappableSelections(ActionContext ignore) {
- // TODO: Use events to track selections/locations
- if (codeViewerService == null || listingService == null) {
- return false;
- }
- ProgramLocation progLoc = codeViewerService.getCurrentLocation();
- ProgramLocation traceLoc = listingService.getCurrentLocation();
-
- if (progLoc == null || traceLoc == null) {
- return false;
- }
-
- ProgramSelection progSel = codeViewerService.getCurrentSelection();
- ProgramSelection traceSel = listingService.getCurrentSelection();
-
- if (progSel != null && progSel.getNumAddressRanges() > 1) {
- return false;
- }
- if (traceSel != null && traceSel.getNumAddressRanges() > 1) {
- return false;
- }
-
- long progLen = progSel == null ? 0 : progSel.getNumAddresses();
- long traceLen = traceSel == null ? 0 : traceSel.getNumAddresses();
- if (progLen == 0 && traceLen == 0) {
- return false;
- }
- return true;
- }
-
private void activatedAdd(ActionContext ignore) {
- // TODO: Use events to track selections/locations
+ tool.showDialog(addMappingDialog);
+ // Try to populate the dialog based on selection, if applicable
+
if (codeViewerService == null || listingService == null) {
return;
}
@@ -303,17 +286,13 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
Address progStart = progLen != 0 ? progSel.getMinAddress() : progLoc.getAddress();
Address traceStart = traceLen != 0 ? traceSel.getMinAddress() : traceLoc.getAddress();
TraceProgramView view = (TraceProgramView) traceLoc.getProgram();
- TraceLocation from =
- new DefaultTraceLocation(currentTrace, null, Range.atLeast(view.getSnap()), traceStart);
- ProgramLocation to = new ProgramLocation(progLoc.getProgram(), progStart);
- try (UndoableTransaction tid =
- UndoableTransaction.start(currentTrace, "Add Static Mapping", false)) {
- mappingService.addMapping(from, to, length, true);
- tid.commit();
+ try {
+ addMappingDialog.setValues(progLoc.getProgram(), currentTrace, progStart, traceStart,
+ length, Range.atLeast(view.getSnap()));
}
- catch (TraceConflictedMappingException e) {
- throw new AssertionError(e); // I said truncateExisting
+ catch (AddressOverflowException e) {
+ Msg.showError(this, null, "Add Mapping", "Error populating dialog");
}
}
@@ -387,5 +366,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
currentTrace = trace;
addNewListeners();
loadMappings();
+
+ addMappingDialog.setTrace(trace);
+ }
+
+ public void setProgram(Program program) {
+ addMappingDialog.setProgram(program);
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/StaticMappingRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/StaticMappingRow.java
index e794e2ea73..bc3447bbb2 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/StaticMappingRow.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/StaticMappingRow.java
@@ -15,6 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.modules;
+import java.math.BigInteger;
import java.net.URL;
import com.google.common.collect.Range;
@@ -24,6 +25,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMapping;
public class StaticMappingRow {
+ private static final BigInteger BIT64 = BigInteger.ONE.shiftLeft(64);
private final TraceStaticMapping mapping;
public StaticMappingRow(TraceStaticMapping mapping) {
@@ -54,6 +56,19 @@ public class StaticMappingRow {
return mapping.getLength();
}
+ public BigInteger getBigLength() {
+ long length = mapping.getLength();
+ if (length == 0) {
+ return BIT64;
+ }
+ else if (length < 0) {
+ return BigInteger.valueOf(length).add(BIT64);
+ }
+ else {
+ return BigInteger.valueOf(length);
+ }
+ }
+
public long getShift() {
return mapping.getShift();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java
index 937cacbcb1..a639ab81a8 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java
@@ -940,31 +940,36 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
if (toURL == null) {
noProject();
}
- try {
- Address start = from.getAddress();
- Address end = start.addNoWrap(length - 1);
- // Also check end in the destination
- Address toAddress = to.getByteAddress();
- toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow
- AddressRangeImpl range = new AddressRangeImpl(start, end);
- if (truncateExisting) {
- long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1;
- for (TraceStaticMapping existing : List
- .copyOf(manager.findAllOverlapping(range, from.getLifespan()))) {
- existing.delete();
- if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) {
- manager.add(existing.getTraceAddressRange(),
- Range.closed(existing.getStartSnap(), truncEnd),
- existing.getStaticProgramURL(), existing.getStaticAddress());
- }
+ Address fromAddress = from.getAddress();
+ Address toAddress = to.getByteAddress();
+ long maxFromLengthMinus1 =
+ fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
+ long maxToLengthMinus1 =
+ toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
+ if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
+ throw new IllegalArgumentException("Length would cause address overflow in trace");
+ }
+ if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
+ throw new IllegalArgumentException("Length would cause address overflow in program");
+ }
+ Address end = fromAddress.addWrap(length - 1);
+ // Also check end in the destination
+ AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
+ Range fromLifespan = from.getLifespan();
+ if (truncateExisting) {
+ long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
+ for (TraceStaticMapping existing : List
+ .copyOf(manager.findAllOverlapping(range, fromLifespan))) {
+ existing.delete();
+ if (fromLifespan.hasLowerBound() &&
+ Long.compare(existing.getStartSnap(), truncEnd) <= 0) {
+ manager.add(existing.getTraceAddressRange(),
+ Range.closed(existing.getStartSnap(), truncEnd),
+ existing.getStaticProgramURL(), existing.getStaticAddress());
}
}
- manager.add(range, from.getLifespan(), toURL,
- toAddress.toString(true));
- }
- catch (AddressOverflowException e) {
- throw new IllegalArgumentException("Length would cause address overflow", e);
}
+ manager.add(range, fromLifespan, toURL, toAddress.toString(true));
}
@Override
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java
index de13a97719..d0b0739975 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java
@@ -535,7 +535,7 @@ public interface DebuggerStaticMappingService {
*
* @param from the source trace location, including lifespan
* @param to the destination program location
- * @param length the length of the mapped region
+ * @param length the length of the mapped region, where 0 indicates {@code 1 << 64}.
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries
* @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and
* {@code truncateExisting} is false.
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProviderTest.java
index 09aa775293..1e5f093b4c 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProviderTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProviderTest.java
@@ -106,7 +106,7 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
@Test
public void testAddAction() throws Exception {
- assertFalse(mappingsProvider.actionAdd.isEnabled());
+ assertTrue(mappingsProvider.actionAdd.isEnabled());
createProgramFromTrace(tb.trace);
intoProject(tb.trace);
@@ -135,16 +135,18 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
programManager.openProgram(program);
waitForSwing();
- assertFalse(mappingsProvider.actionAdd.isEnabled());
-
ProgramSelection traceSel =
new ProgramSelection(tb.addr(0xdeadbeefL), tb.addr(0xdeadbeefL + 0x0f));
listingPlugin.getProvider().setSelection(traceSel);
codeViewerPlugin.goTo(new ProgramLocation(program, addr(program, 0xc0de1234L)), true);
waitForSwing();
- assertTrue(mappingsProvider.actionAdd.isEnabled());
- performAction(mappingsProvider.actionAdd, true);
+ performAction(mappingsProvider.actionAdd, false);
+
+ DebuggerAddMappingDialog dialog = waitForDialogComponent(DebuggerAddMappingDialog.class);
+ dialog.applyCallback();
+ dialog.close();
+ waitForDomainObject(tb.trace);
TraceStaticMapping entry = Unique.assertOne(manager.getAllEntries());
assertEquals(Range.atLeast(0L), entry.getLifespan());
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java
index e7a6168e0d..2d56aa65fb 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java
@@ -76,7 +76,7 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D
public DBTraceStaticMapping add(AddressRange range, Range lifespan, URL toProgramURL,
String toAddress)
throws TraceConflictedMappingException {
- if (lifespan.lowerBoundType() != BoundType.CLOSED) {
+ if (lifespan.hasLowerBound() && lifespan.lowerBoundType() != BoundType.CLOSED) {
throw new IllegalArgumentException("Lower bound must be closed");
}
try (LockHold hold = LockHold.lock(lock.writeLock())) {
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java
index 50f0c7fd4d..70d56010b0 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java
@@ -63,7 +63,7 @@ public interface TraceStaticMapping {
/**
* Get the length of the mapping, i.e., the length of the range
*
- * @return the length
+ * @return the length, where 0 indicates {@code 1 << 64}
*/
long getLength();
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GAddressRangeField.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GAddressRangeField.java
new file mode 100644
index 0000000000..b857d14a54
--- /dev/null
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GAddressRangeField.java
@@ -0,0 +1,209 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.widgets.model;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.util.List;
+import java.util.Objects;
+
+import javax.swing.*;
+
+import ghidra.program.model.address.*;
+import ghidra.util.MathUtilities;
+
+public class GAddressRangeField extends JPanel {
+ private static final Font MONOSPACED = Font.decode("monospaced");
+ private final JComboBox fieldSpace = new JComboBox<>();
+ private final JTextField fieldMin = new JTextField("0");
+ private final JTextField fieldMax = new JTextField("0");
+
+ private final DefaultComboBoxModel modelSpace = new DefaultComboBoxModel<>();
+
+ private AddressFactory factory;
+
+ public GAddressRangeField() {
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+
+ add(new JLabel("["));
+ fieldSpace.setFont(MONOSPACED);
+ add(fieldSpace);
+ add(new JLabel(":"));
+ fieldMin.setFont(MONOSPACED);
+ add(fieldMin);
+ add(new JLabel(", "));
+ fieldMax.setFont(MONOSPACED);
+ add(fieldMax);
+ add(new JLabel("]"));
+
+ fieldSpace.setEditable(false);
+ fieldSpace.setModel(modelSpace);
+
+ fieldSpace.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ spaceFocusLost(e);
+ checkDispatchFocus(e);
+ }
+ });
+
+ fieldMin.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ minFocusLost(e);
+ checkDispatchFocus(e);
+ }
+ });
+ fieldMax.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ maxFocusLost(e);
+ checkDispatchFocus(e);
+ }
+ });
+ }
+
+ protected void checkDispatchFocus(FocusEvent e) {
+ Component opp = e.getOppositeComponent();
+ if (opp == null || !SwingUtilities.isDescendingFrom(opp, this)) {
+ dispatchEvent(e);
+ }
+ }
+
+ public void setAddressFactory(AddressFactory factory) {
+ this.factory = factory;
+
+ modelSpace.removeAllElements();
+
+ if (factory != null) {
+ for (AddressSpace space : factory.getAddressSpaces()) {
+ modelSpace.addElement(space.getName());
+ }
+ modelSpace.setSelectedItem(factory.getDefaultAddressSpace().getName());
+ revalidateMin();
+ revalidateMax();
+ adjustMaxToMin();
+ }
+ }
+
+ protected AddressSpace getSpace(boolean required) {
+ AddressSpace space = factory.getAddressSpace((String) fieldSpace.getSelectedItem());
+ if (required) {
+ return Objects.requireNonNull(space);
+ }
+ return space;
+ }
+
+ protected long parseLong(String text, long defaultVal) {
+ try {
+ return Long.parseUnsignedLong(text, 16);
+ }
+ catch (NumberFormatException ex) {
+ return defaultVal;
+ }
+ }
+
+ protected void revalidateMin() {
+ AddressSpace space = getSpace(true);
+ long spaceMin = space.getMinAddress().getOffset();
+ long min = MathUtilities.unsignedMax(parseLong(fieldMin.getText(), spaceMin), spaceMin);
+
+ fieldMin.setText(Long.toUnsignedString(min, 16));
+ }
+
+ protected void revalidateMax() {
+ AddressSpace space = getSpace(true);
+ long spaceMax = space.getMaxAddress().getOffset();
+ long max = MathUtilities.unsignedMin(parseLong(fieldMax.getText(), spaceMax), spaceMax);
+
+ fieldMax.setText(Long.toUnsignedString(max, 16));
+ }
+
+ protected void adjustMaxToMin() {
+ AddressSpace space = getSpace(true);
+ long spaceMin = space.getMinAddress().getOffset();
+ long min = parseLong(fieldMin.getText(), spaceMin);
+ long max = MathUtilities.unsignedMax(min, parseLong(fieldMax.getText(), min));
+ fieldMax.setText(Long.toUnsignedString(max, 16));
+ }
+
+ protected void adjustMinToMax() {
+ AddressSpace space = getSpace(true);
+ long spaceMax = space.getMaxAddress().getOffset();
+ long max = parseLong(fieldMax.getText(), spaceMax);
+ long min = MathUtilities.unsignedMin(max, parseLong(fieldMin.getText(), max));
+ fieldMin.setText(Long.toUnsignedString(min, 16));
+ }
+
+ protected void spaceFocusLost(FocusEvent e) {
+ if (factory == null) {
+ return;
+ }
+ revalidateMin();
+ revalidateMax();
+ adjustMaxToMin();
+ }
+
+ protected void minFocusLost(FocusEvent e) {
+ if (factory == null) {
+ return;
+ }
+ revalidateMin();
+ adjustMaxToMin();
+ }
+
+ protected void maxFocusLost(FocusEvent e) {
+ if (factory == null) {
+ return;
+ }
+ revalidateMax();
+ adjustMinToMax();
+ }
+
+ public void setRange(AddressRange range) {
+ if (factory == null) {
+ throw new IllegalStateException("Must set address factory first.");
+ }
+ if (!List.of(factory.getAddressSpaces()).contains(range.getAddressSpace())) {
+ throw new IllegalArgumentException(
+ "Given range's space must be in the factory's physical spaces");
+ }
+ fieldSpace.setSelectedItem(range.getAddressSpace().getName());
+ fieldMin.setText(Long.toUnsignedString(range.getMinAddress().getOffset(), 16));
+ fieldMax.setText(Long.toUnsignedString(range.getMaxAddress().getOffset(), 16));
+ }
+
+ public AddressRange getRange() {
+ String name = (String) fieldSpace.getSelectedItem();
+ if (name == null) {
+ return null;
+ }
+ AddressSpace space = Objects.requireNonNull(factory.getAddressSpace(name));
+ long min = Long.parseUnsignedLong(fieldMin.getText(), 16);
+ long max = Long.parseUnsignedLong(fieldMax.getText(), 16);
+ return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ fieldSpace.setEnabled(enabled);
+ fieldMin.setEnabled(enabled);
+ fieldMax.setEnabled(enabled);
+ }
+}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GLifespanField.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GLifespanField.java
new file mode 100644
index 0000000000..732fb09bcd
--- /dev/null
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/model/GLifespanField.java
@@ -0,0 +1,191 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.widgets.model;
+
+import java.awt.Component;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+
+import javax.swing.*;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+
+public class GLifespanField extends JPanel {
+ private static final String NEG_INF = "-\u221e";
+ private static final String POS_INF = "+\u221e";
+
+ private final JLabel labelLower = new JLabel("[");
+ private final JComboBox fieldMin = new JComboBox<>();
+ private final JComboBox fieldMax = new JComboBox<>();
+ private final JLabel labelUpper = new JLabel("]");
+
+ private final DefaultComboBoxModel modelMin = new DefaultComboBoxModel<>();
+ private final DefaultComboBoxModel modelMax = new DefaultComboBoxModel<>();
+
+ public GLifespanField() {
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+
+ add(labelLower);
+ add(fieldMin);
+ add(new JLabel("\u2025"));
+ add(fieldMax);
+ add(labelUpper);
+
+ modelMin.addElement(NEG_INF);
+ modelMax.addElement(POS_INF);
+
+ fieldMin.setEditable(true);
+ fieldMin.setModel(modelMin);
+ fieldMax.setEditable(true);
+ fieldMax.setModel(modelMax);
+
+ fieldMin.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ minFocusLost(e);
+ checkDispatchFocus(e);
+ }
+ });
+ fieldMax.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ maxFocusLost(e);
+ checkDispatchFocus(e);
+ }
+ });
+ }
+
+ protected void checkDispatchFocus(FocusEvent e) {
+ Component opp = e.getOppositeComponent();
+ if (opp == null || !SwingUtilities.isDescendingFrom(opp, this)) {
+ dispatchEvent(e);
+ }
+ }
+
+ protected long parseLong(String text, long defaultVal) {
+ try {
+ return Long.parseLong(text);
+ }
+ catch (NumberFormatException e) {
+ return defaultVal;
+ }
+ }
+
+ protected void revalidateMin() {
+ String value = (String) fieldMin.getSelectedItem();
+ if (NEG_INF.equals(value)) {
+ labelLower.setText("(");
+ }
+ else {
+ fieldMin.setSelectedItem(Long.toString(parseLong(value, 0)));
+ labelLower.setText("[");
+ }
+ }
+
+ protected void revalidateMax() {
+ String value = (String) fieldMax.getSelectedItem();
+ if (POS_INF.equals(value)) {
+ labelUpper.setText(")");
+ }
+ else {
+ fieldMax.setSelectedItem(Long.toString(parseLong(value, 0)));
+ labelUpper.setText("]");
+ }
+ }
+
+ protected void adjustMaxToMin() {
+ if (unbounded()) {
+ return;
+ }
+ long min = parseLong((String) fieldMin.getSelectedItem(), 0);
+ long max = Math.max(min, parseLong((String) fieldMax.getSelectedItem(), min));
+ fieldMax.setSelectedItem(Long.toString(max));
+ }
+
+ protected boolean unbounded() {
+ return NEG_INF.equals(fieldMin.getSelectedItem()) ||
+ POS_INF.equals(fieldMax.getSelectedItem());
+ }
+
+ protected void adjustMinToMax() {
+ if (unbounded()) {
+ return;
+ }
+ long max = parseLong((String) fieldMax.getSelectedItem(), 0);
+ long min = Math.min(max, parseLong((String) fieldMin.getSelectedItem(), max));
+ fieldMin.setSelectedItem(Long.toString(min));
+ }
+
+ protected void minFocusLost(FocusEvent e) {
+ revalidateMin();
+ adjustMaxToMin();
+ }
+
+ protected void maxFocusLost(FocusEvent e) {
+ revalidateMax();
+ adjustMinToMax();
+ }
+
+ public void setLifespan(Range lifespan) {
+ if (lifespan.hasLowerBound() && lifespan.lowerBoundType() == BoundType.OPEN ||
+ lifespan.hasUpperBound() && lifespan.upperBoundType() == BoundType.OPEN) {
+ throw new IllegalArgumentException("Lifespans must be closed or unbounded");
+ }
+
+ if (!lifespan.hasLowerBound()) {
+ fieldMin.setSelectedItem(NEG_INF);
+ }
+ else {
+ fieldMin.setSelectedItem(Long.toString(lifespan.lowerEndpoint()));
+ }
+
+ if (!lifespan.hasUpperBound()) {
+ fieldMax.setSelectedItem(POS_INF);
+ }
+ else {
+ fieldMax.setSelectedItem(Long.toString(lifespan.upperEndpoint()));
+ }
+ }
+
+ public Range getLifespan() {
+ String min = (String) fieldMin.getSelectedItem();
+ String max = (String) fieldMax.getSelectedItem();
+ if (NEG_INF.equals(min)) {
+ if (POS_INF.equals(max)) {
+ return Range.all();
+ }
+ else {
+ return Range.atMost(Long.parseLong(max));
+ }
+ }
+ else {
+ if (POS_INF.equals(max)) {
+ return Range.atLeast(Long.parseLong(min));
+ }
+ else {
+ return Range.closed(Long.parseLong(min), Long.parseLong(max));
+ }
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ fieldMin.setEnabled(enabled);
+ fieldMax.setEnabled(enabled);
+ }
+}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java
index 5063c2813e..2691408886 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java
@@ -32,6 +32,14 @@ public class CustomToStringCellRenderer extends AbstractGColumnRenderer {
DEFAULT, MONOSPACED, BOLD;
}
+ public static String longToPrefixedHexString(long v) {
+ return v < 0 ? "-0x" + Long.toString(-v, 16) : "0x" + Long.toString(v, 16);
+ }
+
+ public static String bigIntToPrefixedHexString(BigInteger v) {
+ return v.signum() < 0 ? "-0x" + v.negate().toString(16) : "0x" + v.toString(16);
+ }
+
public static final CustomToStringCellRenderer