From 3db155ad2c87d43beca33bf1ad32215ade3ac803 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:58:18 -0400 Subject: [PATCH] GP-3357: Add 'Add Region' and 'Delete Regions' actions --- .../DebuggerRegionsPlugin.html | 11 ++ .../core/debug/gui/DebuggerResources.java | 16 -- .../gui/memory/DebuggerAddRegionDialog.java | 184 ++++++++++++++++++ .../gui/memory/DebuggerRegionsProvider.java | 94 ++++++++- .../gui/modules/DebuggerAddMappingDialog.java | 103 +--------- .../core/debug/utils/MiscellaneousUtils.java | 86 ++++++++ .../DebuggerRegionsProviderLegacyTest.java | 8 +- .../memory/DebuggerRegionsProviderTest.java | 21 ++ 8 files changed, 408 insertions(+), 115 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerAddRegionDialog.java diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html index 24ebe105e7..a68e29113a 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html @@ -101,6 +101,17 @@ selects the region containing that cursor. If the dynamic listing has a selection, it selects all regions intersecting that selection.

+

Add Region

+ +

This action is available when a trace is active. It adds a new region to the memory map. It + should only be used for emulation or to correct or diagnose trace recording issues.

+ +

Delete Regions

+ +

This action is available when at least one region is selected. It deletes those regions. Use + this with caution, since recovering those regions could be difficult. In general, this should + only be used to remove regions that were manually added.

+

Force Full View

This action is available when a trace is active. It forces all physical address spaces into diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index f14a5ee3e3..7be4880d25 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -1878,22 +1878,6 @@ public interface DebuggerResources { } } - interface ForceFullViewAction { - String NAME = "Force Full View"; - String DESCRIPTION = "Ignore regions and fiew full address spaces"; - String GROUP = GROUP_GENERAL; - String HELP_ANCHOR = "force_full_view"; - - static ToggleActionBuilder builder(Plugin owner) { - String ownerName = owner.getName(); - return new ToggleActionBuilder(NAME, ownerName) - .description(DESCRIPTION) - .menuGroup(GROUP_GENERAL) - .menuPath(NAME) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); - } - } - interface CompareTimesAction { String NAME = "Compare"; String DESCRIPTION = "Compare this point in time to another"; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerAddRegionDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerAddRegionDialog.java new file mode 100644 index 0000000000..37fd5b948d --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerAddRegionDialog.java @@ -0,0 +1,184 @@ +/* ### + * 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.memory; + +import java.awt.Font; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; + +import db.Transaction; +import docking.ReusableDialogComponentProvider; +import docking.widgets.model.GAddressRangeField; +import docking.widgets.model.GSpanField; +import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceOverlappedRegionException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.layout.PairLayout; + +public class DebuggerAddRegionDialog extends ReusableDialogComponentProvider { + private Trace trace; + + private final JTextField fieldName = new JTextField(); + private final GAddressRangeField fieldRange = new GAddressRangeField(); + private final JTextField fieldLength = new JTextField(); + private final GSpanField fieldLifespan = new GSpanField(); + // NOTE: Flags can be toggled in table + + public DebuggerAddRegionDialog() { + super("Add Region", true, true, true, false); + + populateComponents(); + } + + protected void populateComponents() { + JPanel panel = new JPanel(new PairLayout(5, 5)); + + panel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + panel.add(new JLabel("Name: ")); + panel.add(fieldName); + + panel.add(new JLabel("Range: ")); + panel.add(fieldRange); + + panel.add(new JLabel("Length: ")); + fieldLength.setFont(Font.decode("monospaced")); + panel.add(fieldLength); + + panel.add(new JLabel("Lifespan: ")); + panel.add(fieldLifespan); + + MiscellaneousUtils.rigFocusAndEnter(fieldRange, this::rangeChanged); + MiscellaneousUtils.rigFocusAndEnter(fieldLength, this::lengthChanged); + + fieldLifespan.setLifespan(Lifespan.nowOn(0)); + + addWorkPanel(panel); + + addOKButton(); + addCancelButton(); + } + + protected static AddressRange range(Address min, long lengthMinus1) { + return new AddressRangeImpl(min, min.addWrap(lengthMinus1)); + } + + public void setName(String name) { + fieldName.setText(name); + } + + protected void setFieldLength(long length) { + fieldLength.setText(MiscellaneousUtils.lengthToString(length)); + } + + public long getLength() { + return MiscellaneousUtils.parseLength(fieldLength.getText(), 1); + } + + protected void revalidateLength() { + long length; + if (fieldLength.getText().trim().startsWith("-")) { + length = 1; + } + else { + length = getLength(); + } + length = MiscellaneousUtils.revalidateLengthByRange(fieldRange.getRange(), length); + setFieldLength(length); + } + + protected void adjustLengthToRange() { + AddressRange range = fieldRange.getRange(); + if (range == null) { + return; + } + long length = range.getLength(); + setFieldLength(length); + } + + protected void adjustRangeToLength() { + AddressRange range = fieldRange.getRange(); + if (range == null) { + return; + } + Address min = range.getMinAddress(); + fieldRange.setRange(range(min, getLength() - 1)); + } + + protected void rangeChanged() { + adjustLengthToRange(); + } + + protected void lengthChanged() { + revalidateLength(); + adjustRangeToLength(); + } + + @Override + protected void dialogShown() { + super.dialogShown(); + setStatusText(""); + } + + @Override + protected void cancelCallback() { + setStatusText(""); + super.cancelCallback(); + } + + @Override + protected void okCallback() { + addRegionAndClose(); + } + + protected void setValues(Trace trace, Lifespan lifespan) { + this.trace = trace; + AddressFactory af = trace.getBaseAddressFactory(); + this.fieldRange.setAddressFactory(af); + this.fieldRange.setRange(range(af.getDefaultAddressSpace().getAddress(0), 0)); + this.fieldLength.setText("0x1"); + this.fieldLifespan.setLifespan(lifespan); + } + + public void show(PluginTool tool, Trace trace, long snap) { + setValues(trace, Lifespan.nowOn(snap)); + tool.showDialog(this); + } + + @Override + public void close() { + trace = null; + fieldRange.setAddressFactory(null); + super.close(); + } + + protected void addRegionAndClose() { + try (Transaction tx = trace.openTransaction("Add region: " + fieldName)) { + trace.getMemoryManager() + .addRegion(fieldName.getText(), fieldLifespan.getLifespan(), + fieldRange.getRange()); + close(); + } + catch (TraceOverlappedRegionException | DuplicateNameException e) { + setStatusText(e.getMessage()); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java index d11c6e6c5c..8764d66853 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java @@ -24,15 +24,18 @@ import javax.swing.*; import org.apache.commons.lang3.ArrayUtils; +import db.Transaction; import docking.ActionContext; import docking.WindowPosition; import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerBlockChooserDialog; import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectRowsAction; import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProvider; import ghidra.app.plugin.core.debug.service.modules.MapRegionsBackgroundCommand; @@ -76,7 +79,8 @@ public class DebuggerRegionsProvider 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) .popupMenuPath(NAME) .popupMenuGroup(GROUP) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); @@ -91,7 +95,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME_PREFIX, ownerName).description(DESCRIPTION) + return new ActionBuilder(NAME_PREFIX, ownerName) + .description(DESCRIPTION) .popupMenuPath(NAME_PREFIX + "...") .popupMenuGroup(GROUP) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); @@ -107,13 +112,62 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME_PREFIX, ownerName).description(DESCRIPTION) + return new ActionBuilder(NAME_PREFIX, ownerName) + .description(DESCRIPTION) .popupMenuPath(NAME_PREFIX + "...") .popupMenuGroup(GROUP) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } + interface AddRegionAction { + String NAME = "Add Region"; + String DESCRIPTION = "Manually add a region to the memory map"; + String GROUP = DebuggerResources.GROUP_MAINTENANCE; + String HELP_ANCHOR = "add_region"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface DeleteRegionsAction { + String NAME = "Delete Regions"; + String DESCRIPTION = "Delete one or more regions from the memory map"; + String GROUP = DebuggerResources.GROUP_MAINTENANCE; + String HELP_ANCHOR = "delete_regions"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .popupMenuGroup(GROUP) + .popupMenuPath(NAME, "Yes, really. Delete them!") + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface ForceFullViewAction { + String NAME = "Force Full View"; + String DESCRIPTION = "Ignore regions and fiew full address spaces"; + String GROUP = DebuggerResources.GROUP_GENERAL; + String HELP_ANCHOR = "force_full_view"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + protected class SelectAddressesAction extends AbstractSelectAddressesAction { public static final String GROUP = DebuggerResources.GROUP_GENERAL; @@ -175,12 +229,16 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { // TODO: Lazy construction of these dialogs? private final DebuggerBlockChooserDialog blockChooserDialog; private final DebuggerRegionMapProposalDialog regionProposalDialog; + private final DebuggerAddRegionDialog addRegionDialog; + DockingAction actionMapRegions; DockingAction actionMapRegionTo; DockingAction actionMapRegionsTo; SelectAddressesAction actionSelectAddresses; DockingAction actionSelectRows; + DockingAction actionAddRegion; + DockingAction actionDeleteRegions; ToggleDockingAction actionForceFullView; public DebuggerRegionsProvider(DebuggerRegionsPlugin plugin) { @@ -198,6 +256,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { blockChooserDialog = new DebuggerBlockChooserDialog(tool); regionProposalDialog = new DebuggerRegionMapProposalDialog(this); + addRegionDialog = new DebuggerAddRegionDialog(); setDefaultWindowPosition(WindowPosition.BOTTOM); setVisible(true); @@ -237,6 +296,14 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { .enabledWhen(ctx -> current.getTrace() != null) .onAction(this::activatedSelectCurrent) .buildAndInstallLocal(this); + actionAddRegion = AddRegionAction.builder(plugin) + .enabledWhen(ctx -> current.getTrace() != null) + .onAction(this::activatedAddRegion) + .buildAndInstallLocal(this); + actionDeleteRegions = DeleteRegionsAction.builder(plugin) + .enabledWhen(this::isContextNonEmpty) + .onAction(this::activatedDeleteRegions) + .buildAndInstallLocal(this); actionForceFullView = ForceFullViewAction.builder(plugin) .enabledWhen(ctx -> current.getTrace() != null) .onAction(this::activatedForceFullView) @@ -385,6 +452,25 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { } } + private void activatedAddRegion(ActionContext ignored) { + if (current.getTrace() == null) { + return; + } + addRegionDialog.show(tool, current.getTrace(), current.getSnap()); + } + + private void activatedDeleteRegions(ActionContext ctx) { + Set sel = getSelectedRegions(ctx); + if (sel.isEmpty()) { + return; + } + try (Transaction tx = current.getTrace().openTransaction("Delete regions")) { + for (TraceMemoryRegion region : sel) { + region.delete(); + } + } + } + private void activatedForceFullView(ActionContext ignored) { if (current.getTrace() == null) { return; 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 index 355d496f63..cf0a28bff0 100644 --- 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 @@ -15,10 +15,7 @@ */ 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; @@ -26,6 +23,7 @@ import javax.swing.border.EmptyBorder; import docking.ReusableDialogComponentProvider; import docking.widgets.model.GAddressRangeField; import docking.widgets.model.GSpanField; +import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.framework.model.DomainFile; import ghidra.program.model.address.*; @@ -33,13 +31,10 @@ 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.MessageType; import ghidra.util.layout.PairLayout; public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { - private static final String HEX_BIT64 = "0x" + BigInteger.ONE.shiftLeft(64).toString(16); - private DebuggerStaticMappingService mappingService; private Program program; @@ -58,23 +53,6 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { 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)); @@ -99,10 +77,10 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { panel.add(new JLabel("Lifespan: ")); panel.add(fieldSpan); - rigFocusAndEnter(fieldProgRange, this::progRangeChanged); - rigFocusAndEnter(fieldTraceRange, this::traceRangeChanged); - rigFocusAndEnter(fieldLength, this::lengthChanged); - rigFocusAndEnter(fieldSpan, this::spanChanged); + MiscellaneousUtils.rigFocusAndEnter(fieldProgRange, this::progRangeChanged); + MiscellaneousUtils.rigFocusAndEnter(fieldTraceRange, this::traceRangeChanged); + MiscellaneousUtils.rigFocusAndEnter(fieldLength, this::lengthChanged); + MiscellaneousUtils.rigFocusAndEnter(fieldSpan, this::spanChanged); fieldSpan.setLifespan(Lifespan.nowOn(0)); @@ -149,33 +127,12 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { 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)); - } + fieldLength.setText(MiscellaneousUtils.lengthToString(length)); } public long getLength() { - return parseLength(fieldLength.getText(), 1); + return MiscellaneousUtils.parseLength(fieldLength.getText(), 1); } protected void revalidateLength() { @@ -186,8 +143,8 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { else { length = getLength(); } - length = revalidateLengthByRange(fieldProgRange.getRange(), length); - length = revalidateLengthByRange(fieldTraceRange.getRange(), length); + length = MiscellaneousUtils.revalidateLengthByRange(fieldProgRange.getRange(), length); + length = MiscellaneousUtils.revalidateLengthByRange(fieldTraceRange.getRange(), length); setFieldLength(length); } @@ -248,48 +205,6 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { 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 dialogShown() { super.dialogShown(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java index 618a38e4d4..874b849016 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/MiscellaneousUtils.java @@ -16,17 +16,24 @@ package ghidra.app.plugin.core.debug.utils; import java.awt.Component; +import java.awt.event.*; import java.beans.PropertyEditor; +import java.math.BigInteger; import java.util.Map; import java.util.function.Function; import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; import ghidra.framework.options.*; +import ghidra.program.model.address.AddressRange; +import ghidra.util.MathUtilities; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; public enum MiscellaneousUtils { ; + + public static final String HEX_BIT64 = "0x" + BigInteger.ONE.shiftLeft(64).toString(16); + /** * Obtain a swing component which may be used to edit the property. * @@ -63,6 +70,23 @@ public enum MiscellaneousUtils { "Ghidra does not know how to use PropertyEditor: " + editor.getClass().getName()); } + public 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(); + } + } + }); + } + public static void collectUniqueInstances(Class cls, Map map, Function keyFunc) { // This is wasteful. Existing instances will be re-instantiated and thrown away @@ -79,4 +103,66 @@ public enum MiscellaneousUtils { map.put(key, t); } } + + public static long lengthMin(long a, long b) { + if (a == 0) { + return b; + } + if (b == 0) { + return a; + } + return MathUtilities.unsignedMin(a, b); + } + + public static String lengthToString(long length) { + return length == 0 ? HEX_BIT64 : ("0x" + Long.toHexString(length)); + } + + /** + * 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}. + */ + public 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. + } + + public static long revalidateLengthByRange(AddressRange range, long length) { + long maxLength = + range.getAddressSpace().getMaxAddress().subtract(range.getMinAddress()) + 1; + return MiscellaneousUtils.lengthMin(length, maxLength); + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java index 618b6d2728..7908510505 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderLegacyTest.java @@ -15,7 +15,8 @@ */ package ghidra.app.plugin.core.debug.gui.memory; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import java.util.*; @@ -354,6 +355,11 @@ public class DebuggerRegionsProviderLegacyTest extends AbstractGhidraHeadedDebug new AddressSet(listing.getSelection()))); } + @Test + public void testActionAddRegion() throws Exception { + createAndOpenTrace(); + } + @Test public void testActionSelectRows() throws Exception { addPlugin(tool, DebuggerListingPlugin.class); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java index 6638f23661..db8a4f1981 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java @@ -25,6 +25,7 @@ import org.junit.experimental.categories.Category; import db.Transaction; import docking.widgets.table.DynamicTableColumn; +import generic.Unique; import generic.test.category.NightlyCategory; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; @@ -47,6 +48,7 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramSelection; import ghidra.trace.model.Lifespan; import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceObjectMemoryRegion; import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.trace.model.target.*; @@ -471,6 +473,25 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI new AddressSet(listing.getSelection()))); } + @Test + public void testActionAddRegion() throws Exception { + createAndOpenTrace(); + traceManager.activateTrace(tb.trace); + + performEnabledAction(provider, provider.actionAddRegion, false); + DebuggerAddRegionDialog dialog = waitForDialogComponent(DebuggerAddRegionDialog.class); + runSwing(() -> { + dialog.setName("Memory[heap]"); + dialog.setFieldLength(0x1000); + dialog.lengthChanged(); // simulate ENTER/focus-exited + dialog.okCallback(); + }); + waitForSwing(); + + TraceMemoryRegion region = Unique.assertOne(tb.trace.getMemoryManager().getAllRegions()); + assertEquals(tb.range(0, 0xfff), region.getRange()); + } + @Test public void testActionSelectRows() throws Exception { addPlugin(tool, DebuggerListingPlugin.class);