Merge remote-tracking branch 'origin/GP-3357_Dan_addRegionAction--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-04-28 11:32:28 -04:00
8 changed files with 408 additions and 115 deletions
@@ -101,6 +101,17 @@
selects the region containing that cursor. If the dynamic listing has a selection, it selects
all regions intersecting that selection.</P>
<H3><A name="add_region"></A>Add Region</H3>
<P>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.</P>
<H3><A name="delete_regions"></A>Delete Regions</H3>
<P>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.</P>
<H3><A name="force_full_view"></A>Force Full View</H3>
<P>This action is available when a trace is active. It forces all physical address spaces into
@@ -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";
@@ -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());
}
}
}
@@ -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<TraceMemoryRegion> 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;
@@ -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.
*
* <p>
* 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();
@@ -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 <T> void collectUniqueInstances(Class<T> cls, Map<String, T> map,
Function<T, String> 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.
*
* <p>
* 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);
}
}
@@ -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);
@@ -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);