diff --git a/Ghidra/Features/Base/data/base.icons.theme.properties b/Ghidra/Features/Base/data/base.icons.theme.properties
index 3792d7c068..9afdb5f3d0 100644
--- a/Ghidra/Features/Base/data/base.icons.theme.properties
+++ b/Ghidra/Features/Base/data/base.icons.theme.properties
@@ -396,4 +396,4 @@ icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOu
-[Dark Defaults]
\ No newline at end of file
+[Dark Defaults]
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/SelectionTable.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/SelectionTable.htm
index ca7c5ba4cc..1809a36ff0 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/SelectionTable.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/SelectionTable.htm
@@ -13,7 +13,7 @@
- Selection Table
+ Selection Tables
The Create Table From Selection action will create a table of addresses based upon
the current selection in the Listing. Each entry in
@@ -30,6 +30,25 @@
"help/shared/arrow.gif">Create Table From Selection from the tool's menu bar.
+ The Create Table From Ranges action will create a table based on the
+ current selection. Each row of the table corresponds to an address range in the selection.
+
+ Range Table Actions
+
+ There are two special actions available on an address range table:
+
+ -
+ Select Minimum Addresses
+
This action creates a program selection containing the minimum address of each selected
+ address range.
+
+ -
+ Select Maximum Addresses
+
This action creates a program selection containing the maximum address of each selected
+ address range.
+
+
+
Provided by: Code Browser plugin
Related Topics:
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeBytesTableColumn.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeBytesTableColumn.java
new file mode 100644
index 0000000000..c19462e7cb
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeBytesTableColumn.java
@@ -0,0 +1,127 @@
+/* ###
+ * 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.codebrowser;
+
+import docking.widgets.table.AbstractDynamicTableColumn;
+import ghidra.docking.settings.*;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.program.model.address.*;
+import ghidra.program.model.data.EndianSettingsDefinition;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.util.table.column.GColumnRenderer;
+import ghidra.util.table.field.*;
+
+/**
+ * A column for displaying a small window of bytes around the endpoints of an {@link AddressRange}
+ * in an address range table
+ */
+public class AddressRangeBytesTableColumn
+ extends AbstractDynamicTableColumn {
+
+ private final GColumnRenderer monospacedRenderer = new MonospacedByteRenderer();
+ private static SettingsDefinition[] SETTINGS =
+ { ByteCountSettingsDefinition.DEF, MemoryOffsetSettingsDefinition.DEF,
+ EndianSettingsDefinition.DEF, FormatSettingsDefinition.DEF,
+ AddressRangeEndpointSettingsDefinition.DEF };
+ private static final String COLUMN_NAME = "Bytes";
+
+ /**
+ * Default constructor
+ */
+ public AddressRangeBytesTableColumn() {
+ }
+
+ @Override
+ public String getColumnName() {
+ return COLUMN_NAME;
+ }
+
+ @Override
+ public Byte[] getValue(AddressRangeInfo rowObject, Settings settings, Program program,
+ ServiceProvider serviceProvider) throws IllegalArgumentException {
+ int choice = AddressRangeEndpointSettingsDefinition.DEF.getChoice(settings);
+ Address base =
+ choice == AddressRangeEndpointSettingsDefinition.BEGIN_CHOICE_INDEX ? rowObject.min()
+ : rowObject.max();
+ int offset = MemoryOffsetSettingsDefinition.DEF.getOffset(settings);
+ int byteCount = ByteCountSettingsDefinition.DEF.getChoice(settings);
+ byte[] bytes = null;
+ //default: display the bytes in the associated CodeUnit
+ //unless there is a nonzero offset. In that case, display the one byte at that offset
+ try {
+ base = base.addNoWrap(offset);
+ if (byteCount == ByteCountSettingsDefinition.DEFAULT) {
+ if (offset != 0) {
+ byteCount = 1;
+ }
+ else {
+ CodeUnit cu = program.getListing().getCodeUnitContaining(base);
+ if (cu == null) { // can happen for 'SpecialAddress'es
+ return new Byte[0];
+ }
+ if (cu instanceof Instruction instr) {
+ bytes = instr.getParsedBytes();
+ }
+ else {
+ bytes = cu.getBytes();
+ }
+ }
+ }
+ if (bytes == null) {
+ bytes = new byte[byteCount];
+ program.getMemory().getBytes(base, bytes);
+ }
+
+ Byte[] bytesObj = new Byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ bytesObj[i] = Byte.valueOf(bytes[i]);
+ }
+ return bytesObj;
+ }
+ catch (MemoryAccessException | AddressOverflowException e) {
+ return new Byte[0];
+ }
+ }
+
+ @Override
+ public GColumnRenderer getColumnRenderer() {
+ return monospacedRenderer;
+ }
+
+ @Override
+ public SettingsDefinition[] getSettingsDefinitions() {
+ return SETTINGS;
+ }
+
+ @Override
+ public String getColumnDisplayName(Settings settings) {
+ StringBuilder sb =
+ new StringBuilder(AddressRangeEndpointSettingsDefinition.DEF.getValueString(settings));
+ sb.append(" ").append(COLUMN_NAME);
+ int byteCnt = ByteCountSettingsDefinition.DEF.getChoice(settings);
+ if (byteCnt != ByteCountSettingsDefinition.DEFAULT) {
+ sb.append("[");
+ sb.append(byteCnt);
+ sb.append("]");
+ }
+ String offset = MemoryOffsetSettingsDefinition.DEF.getValueString(settings);
+ if (!offset.equals("0")) {
+ sb.append(offset);
+ }
+ return sb.toString();
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeCodeUnitTableColumn.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeCodeUnitTableColumn.java
new file mode 100644
index 0000000000..e04a586a39
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeCodeUnitTableColumn.java
@@ -0,0 +1,109 @@
+/* ###
+ * 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.codebrowser;
+
+import docking.widgets.table.AbstractDynamicTableColumn;
+import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
+import ghidra.docking.settings.Settings;
+import ghidra.docking.settings.SettingsDefinition;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.listing.*;
+import ghidra.program.util.ProgramLocation;
+import ghidra.util.table.CodeUnitTableCellRenderer;
+import ghidra.util.table.column.GColumnRenderer;
+import ghidra.util.table.field.*;
+
+/**
+ * A column for displaying a small window of {@link CodeUnit}s around a selected endpoint
+ * of an {@link AddressRange} in an address range table
+ */
+public class AddressRangeCodeUnitTableColumn
+ extends AbstractDynamicTableColumn {
+
+ private static SettingsDefinition[] SETTINGS = { CodeUnitCountSettingsDefinition.DEF,
+ CodeUnitOffsetSettingsDefinition.DEF, AddressRangeEndpointSettingsDefinition.DEF };
+
+ private final CodeUnitTableCellRenderer renderer = new CodeUnitTableCellRenderer();
+ private CodeUnitFormat codeUnitFormat;
+ private static final String COLUMN_NAME = "Code Unit";
+
+ /**
+ * Default constructor
+ */
+ public AddressRangeCodeUnitTableColumn() {
+ }
+
+ @Override
+ public String getColumnName() {
+ return COLUMN_NAME;
+ }
+
+ @Override
+ public String getColumnDisplayName(Settings settings) {
+ StringBuilder sb =
+ new StringBuilder(AddressRangeEndpointSettingsDefinition.DEF.getValueString(settings));
+ sb.append(" ").append(COLUMN_NAME);
+ int previewCnt = CodeUnitCountSettingsDefinition.DEF.getCount(settings);
+ if (previewCnt != 1) {
+ sb.append("[");
+ sb.append(previewCnt);
+ sb.append("]");
+ }
+ String offset = CodeUnitOffsetSettingsDefinition.DEF.getDisplayValue(settings);
+ if (!"0".equals(offset)) {
+ sb.append(offset);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public CodeUnitTableCellData getValue(AddressRangeInfo rowObject, Settings settings,
+ Program program, ServiceProvider serviceProvider) throws IllegalArgumentException {
+ int choice = AddressRangeEndpointSettingsDefinition.DEF.getChoice(settings);
+ Address base =
+ choice == AddressRangeEndpointSettingsDefinition.BEGIN_CHOICE_INDEX ? rowObject.min()
+ : rowObject.max();
+ ProgramLocation location = new ProgramLocation(program, base);
+ return new CodeUnitTableCellData(location, getCodeUnitFormat(serviceProvider),
+ CodeUnitOffsetSettingsDefinition.DEF.getOffset(settings),
+ CodeUnitCountSettingsDefinition.DEF.getCount(settings));
+ }
+
+ @Override
+ public GColumnRenderer getColumnRenderer() {
+ return renderer;
+ }
+
+ @Override
+ public SettingsDefinition[] getSettingsDefinitions() {
+ return SETTINGS;
+ }
+
+ @Override
+ public int getMaxLines(Settings settings) {
+ return CodeUnitCountSettingsDefinition.DEF.getCount(settings);
+ }
+
+ private CodeUnitFormat getCodeUnitFormat(ServiceProvider serviceProvider) {
+ if (codeUnitFormat == null) {
+ codeUnitFormat = new BrowserCodeUnitFormat(serviceProvider);
+ }
+ return codeUnitFormat;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeInfo.java
new file mode 100644
index 0000000000..a2a83523cf
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeInfo.java
@@ -0,0 +1,75 @@
+/* ###
+ * 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.codebrowser;
+
+import java.util.Objects;
+
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryAccessException;
+
+/**
+ * A record for information about an {@link AddressRange}, used when creating
+ * address range tables
+ *
+ * @param min smallest address in range
+ * @param max largest address in range
+ * @param size number of addresses in range
+ * @param isSameByte true precisely when all of the bytes in the range have the same value or
+ * all are undefined
+ * @param numRefsTo number of references to this range
+ * @param numRefsFrom number of references out of this range
+ */
+public record AddressRangeInfo(Address min, Address max, long size, boolean isSameByte,
+ int numRefsTo, int numRefsFrom) {
+
+ /**
+ * Returns true precisely when all of the addresses between min and max (inclusive)
+ * have the same byte value OR all addresses are without values
+ *
+ * @param min minimum address
+ * @param max maximum address
+ * @param program program
+ * @return true if all addresses have same value
+ */
+ public static boolean isSameByteValue(Address min, Address max, Program program) {
+ //throws an IllegalArgumentException if min,max don't defined a proper AddressRange
+ AddressSet range = new AddressSet(min, max);
+ AddressSetView loadedAndInit = program.getMemory().getLoadedAndInitializedAddressSet();
+ if (!range.intersects(loadedAndInit)) {
+ return true; //range has no values
+ }
+ if (!loadedAndInit.contains(range)) {
+ return false; //range contains some address with values and some without
+ //this might be impossible
+ }
+ try {
+ Byte firstByte = program.getMemory().getByte(min);
+ for (Address addr = min.add(1); addr.compareTo(max) <= 0; addr = addr.add(1)) {
+ Byte val = program.getMemory().getByte(addr);
+
+ if (!Objects.equals(val, firstByte)) {
+ return false;
+ }
+ }
+ }
+ catch (MemoryAccessException e) {
+ return false; //shouldn't happen
+ }
+ return true;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeTableModel.java
new file mode 100644
index 0000000000..2f6436fcd1
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AddressRangeTableModel.java
@@ -0,0 +1,254 @@
+/* ###
+ * 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.codebrowser;
+
+import java.util.stream.StreamSupport;
+
+import docking.widgets.table.AbstractDynamicTableColumn;
+import docking.widgets.table.TableColumnDescriptor;
+import ghidra.docking.settings.Settings;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.ReferenceManager;
+import ghidra.program.util.ProgramLocation;
+import ghidra.program.util.ProgramSelection;
+import ghidra.util.Msg;
+import ghidra.util.datastruct.Accumulator;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.table.GhidraProgramTableModel;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * A {@link GhidraProgramTableModel} for displaying tables in which one row corresponds
+ * to an {@link AddressRange}
+ */
+public class AddressRangeTableModel extends GhidraProgramTableModel {
+
+ private ProgramSelection selection;
+ private static final int MAX_ADDRESS_COLUMN_INDEX = 1;
+ private int resultsLimit;
+ private long minLength;
+
+ protected AddressRangeTableModel(PluginTool tool, Program program, ProgramSelection selection,
+ int resultsLimit, long minLength) {
+ super("Selected Ranges in " + program.getName(), tool, program, null);
+ this.selection = selection;
+ this.resultsLimit = resultsLimit;
+ this.minLength = minLength;
+ }
+
+ @Override
+ public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
+ AddressRangeInfo rangeInfo = getRowObject(modelRow);
+ if (modelColumn == MAX_ADDRESS_COLUMN_INDEX) {
+ return new ProgramLocation(program, rangeInfo.max());
+ }
+ return new ProgramLocation(program, rangeInfo.min());
+ }
+
+ @Override
+ public ProgramSelection getProgramSelection(int[] modelRows) {
+ AddressSet ranges = new AddressSet();
+ for (AddressRangeInfo rangeInfo : getRowObjects(modelRows)) {
+ ranges.addRange(program, rangeInfo.min(), rangeInfo.max());
+ }
+ return new ProgramSelection(program.getAddressFactory(), ranges);
+ }
+
+ @Override
+ protected void doLoad(Accumulator accumulator, TaskMonitor monitor)
+ throws CancelledException {
+ AddressRangeIterator rangeIter = selection.getAddressRanges();
+ ReferenceManager refManager = program.getReferenceManager();
+ while (rangeIter.hasNext()) {
+ monitor.checkCancelled();
+ AddressRange range = rangeIter.next();
+ if (range.getLength() < minLength) {
+ continue;
+ }
+ boolean isSameByte = AddressRangeInfo.isSameByteValue(range.getMinAddress(),
+ range.getMaxAddress(), program);
+
+ AddressSet rangeSet = new AddressSet(range);
+
+ AddressIterator destAddrIter =
+ refManager.getReferenceDestinationIterator(rangeSet, true);
+ int numRefsTo = StreamSupport.stream(destAddrIter.spliterator(), false)
+ .map(addr -> refManager.getReferenceCountTo(addr))
+ .reduce(0, Integer::sum);
+
+ AddressIterator srcAddrIter =
+ refManager.getReferenceSourceIterator(rangeSet, true);
+ int numRefsFrom = StreamSupport.stream(srcAddrIter.spliterator(), false)
+ .map(addr -> refManager.getReferenceCountFrom(addr))
+ .reduce(0, Integer::sum);
+
+ AddressRangeInfo info = new AddressRangeInfo(range.getMinAddress(),
+ range.getMaxAddress(), range.getLength(), isSameByte, numRefsTo, numRefsFrom);
+
+ accumulator.add(info);
+ if (accumulator.size() >= resultsLimit) {
+ Msg.showWarn(this, null, "Results Truncated",
+ "Results are limited to " + resultsLimit + " address ranges.\n" +
+ "This limit can be changed by the tool option \"" +
+ CodeBrowserSelectionPlugin.OPTION_CATEGORY_NAME + " -> " +
+ CodeBrowserSelectionPlugin.RANGES_LIMIT_OPTION_NAME + "\".");
+ break;
+ }
+ }
+ if (accumulator.isEmpty()) {
+ Msg.showWarn(this, null, "No Ranges to Display",
+ "No ranges to display - consider adjusting \"" +
+ CodeBrowserSelectionPlugin.OPTION_CATEGORY_NAME + " -> " +
+ CodeBrowserSelectionPlugin.MIN_RANGE_SIZE_OPTION_NAME + "\".");
+ }
+ }
+
+ @Override
+ public void refresh() {
+ reload();
+ }
+
+ @Override
+ protected TableColumnDescriptor createTableColumnDescriptor() {
+ TableColumnDescriptor descriptor = new TableColumnDescriptor<>();
+ descriptor.addVisibleColumn(new MinAddressTableColumn());
+ descriptor.addVisibleColumn(new MaxAddressTableColumn());
+ descriptor.addVisibleColumn(new LengthTableColumn());
+ descriptor.addVisibleColumn(new IdenticalBytesTableColumn());
+ descriptor.addVisibleColumn(new NumRefsToTableColumn());
+ descriptor.addVisibleColumn(new NumRefsFromTableColumn());
+ descriptor.addVisibleColumn(new BlockNameTableColumn());
+ descriptor.addHiddenColumn(new AddressRangeBytesTableColumn());
+ descriptor.addHiddenColumn(new AddressRangeCodeUnitTableColumn());
+
+ return descriptor;
+ }
+
+ private class MinAddressTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Min Address";
+ }
+
+ @Override
+ public Address getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.min();
+ }
+ }
+
+ private class MaxAddressTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Max Address";
+ }
+
+ @Override
+ public Address getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.max();
+ }
+ }
+
+ private class LengthTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Length";
+ }
+
+ @Override
+ public Long getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.size();
+ }
+ }
+
+ private class IdenticalBytesTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Identical Bytes";
+ }
+
+ @Override
+ public String getColumnDescription() {
+ return "Do all bytes in the range have the same value";
+ }
+
+ @Override
+ public Boolean getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.isSameByte();
+ }
+ }
+
+ private class BlockNameTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Block Name";
+ }
+
+ @Override
+ public String getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return program.getMemory().getBlock(rangeInfo.min()).getName();
+ }
+ }
+
+ private class NumRefsToTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "To References";
+ }
+
+ @Override
+ public Integer getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.numRefsTo();
+ }
+ }
+
+ private class NumRefsFromTableColumn
+ extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "From References";
+ }
+
+ @Override
+ public Integer getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
+ ServiceProvider services) throws IllegalArgumentException {
+ return rangeInfo.numRefsFrom();
+ }
+ }
+
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java
index f83d7a041c..50807a6300 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserSelectionPlugin.java
@@ -20,13 +20,14 @@ import javax.swing.Icon;
import docking.action.builder.ActionBuilder;
import docking.tool.ToolConstants;
import generic.theme.GIcon;
-import ghidra.GhidraOptions;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.core.codebrowser.SelectEndpointsAction.RangeEndpoint;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.SearchConstants;
import ghidra.app.util.query.TableService;
+import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@@ -57,10 +58,22 @@ public class CodeBrowserSelectionPlugin extends Plugin {
private static final String SELECT_GROUP = "Select Group";
private static final String SELECTION_LIMIT_OPTION_NAME = "Table From Selection Limit";
+ private static final int SELECTION_LIMIT_DEFAULT = 500;
+ static final String RANGES_LIMIT_OPTION_NAME = "Ranges From Selection Limit";
+ static final int RANGES_LIMIT_DEFAULT = 500;
+ static final String MIN_RANGE_SIZE_OPTION_NAME =
+ "Minimum Length of Address Range in Range Table";
+ static final long MIN_RANGE_SIZE_DEFAULT = 1;
+
+ static final String CREATE_ADDRESS_RANGE_TABLE_ACTION_NAME =
+ "Create Table From Ranges";
+
+ static final String OPTION_CATEGORY_NAME = "Selection Tables";
public CodeBrowserSelectionPlugin(PluginTool tool) {
super(tool);
createActions();
+ initializeOptions();
}
private void createActions() {
@@ -98,11 +111,57 @@ public class CodeBrowserSelectionPlugin extends Plugin {
new ActionBuilder("Create Table From Selection", getName())
.menuPath(ToolConstants.MENU_SELECTION, "Create Table From Selection")
.menuGroup("SelectUtils")
- .helpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table"))
+ .helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Selection_Tables"))
.withContext(CodeViewerActionContext.class, true)
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
.onAction(c -> createTable((CodeViewerProvider) c.getComponentProvider()))
.buildAndInstall(tool);
+
+ new ActionBuilder(CREATE_ADDRESS_RANGE_TABLE_ACTION_NAME, getName())
+ .menuPath(ToolConstants.MENU_SELECTION, CREATE_ADDRESS_RANGE_TABLE_ACTION_NAME)
+ .menuGroup("SelectUtils")
+ .helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Selection_Tables"))
+ .withContext(CodeViewerActionContext.class, true)
+ .inWindow(ActionBuilder.When.CONTEXT_MATCHES)
+ .onAction(
+ c -> createAddressRangeTable((CodeViewerProvider) c.getComponentProvider()))
+ .buildAndInstall(tool);
+
+ }
+
+ private void createAddressRangeTable(CodeViewerProvider componentProvider) {
+ TableService tableService = tool.getService(TableService.class);
+ if (tableService == null) {
+ Msg.showWarn(this, null, "No Table Service", "Please add the TableServicePlugin.");
+ return;
+ }
+ Program program = componentProvider.getProgram();
+ ProgramSelection selection = componentProvider.getSelection();
+ if (selection.isEmpty()) {
+ tool.setStatusInfo("Unable to create selected ranges table: no addresses in selection");
+ return;
+ }
+ ToolOptions options = tool.getOptions(OPTION_CATEGORY_NAME);
+ int resultsLimit = options.getInt(RANGES_LIMIT_OPTION_NAME, RANGES_LIMIT_DEFAULT);
+ long minLength = options.getLong(MIN_RANGE_SIZE_OPTION_NAME, MIN_RANGE_SIZE_DEFAULT);
+ AddressRangeTableModel model =
+ new AddressRangeTableModel(tool, program, selection, resultsLimit, minLength);
+ Icon markerIcon = new GIcon("icon.plugin.codebrowser.cursor.marker");
+ String title = "Selected Ranges in " + program.getName();
+ TableComponentProvider tableProvider =
+ tableService.showTableWithMarkers(title, "Selected Ranges", model,
+ SearchConstants.SEARCH_HIGHLIGHT_COLOR, markerIcon, title, null);
+ tableProvider.installRemoveItemsAction();
+
+ SelectEndpointsAction selectMin =
+ new SelectEndpointsAction(this, program, model, RangeEndpoint.MIN);
+ selectMin.setHelpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Range_Table_Actions"));
+ tableProvider.addLocalAction(selectMin);
+
+ SelectEndpointsAction selectMax =
+ new SelectEndpointsAction(this, program, model, RangeEndpoint.MAX);
+ selectMax.setHelpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Range_Table_Actions"));
+ tableProvider.addLocalAction(selectMax);
}
@@ -156,9 +215,8 @@ public class CodeBrowserSelectionPlugin extends Plugin {
public void load(Accumulator accumulator, TaskMonitor monitor)
throws CancelledException {
- ToolOptions options = tool.getOptions(ToolConstants.TOOL_OPTIONS);
- int resultsLimit = options.getInt(GhidraOptions.OPTION_SEARCH_LIMIT,
- SearchConstants.DEFAULT_SEARCH_LIMIT);
+ ToolOptions options = tool.getOptions(OPTION_CATEGORY_NAME);
+ int resultsLimit = options.getInt(SELECTION_LIMIT_OPTION_NAME, SELECTION_LIMIT_DEFAULT);
long size = selection.getNumAddresses();
monitor.initialize(size);
@@ -167,7 +225,8 @@ public class CodeBrowserSelectionPlugin extends Plugin {
if (accumulator.size() >= resultsLimit) {
Msg.showWarn(this, null, "Results Truncated",
"Results are limited to " + resultsLimit + " code units.\n" +
- "This limit can be changed by the tool option \"Tool -> " +
+ "This limit can be changed by the tool option \"" +
+ OPTION_CATEGORY_NAME + " -> " +
SELECTION_LIMIT_OPTION_NAME +
"\".");
break;
@@ -179,4 +238,16 @@ public class CodeBrowserSelectionPlugin extends Plugin {
}
}
}
+
+ private void initializeOptions() {
+ Options opt = tool.getOptions(OPTION_CATEGORY_NAME);
+ opt.registerOption(SELECTION_LIMIT_OPTION_NAME, SELECTION_LIMIT_DEFAULT, null,
+ "Maximum number of entries in selection table");
+
+ opt.registerOption(RANGES_LIMIT_OPTION_NAME, RANGES_LIMIT_DEFAULT, null,
+ "Maximum number of entries in an address range table");
+
+ opt.registerOption(MIN_RANGE_SIZE_OPTION_NAME, MIN_RANGE_SIZE_DEFAULT, null,
+ "Minimum length of an address range in address range table");
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/SelectEndpointsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/SelectEndpointsAction.java
new file mode 100644
index 0000000000..3e603ec00f
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/SelectEndpointsAction.java
@@ -0,0 +1,138 @@
+/* ###
+ * 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.codebrowser;
+
+import java.awt.Component;
+import java.util.*;
+
+import javax.swing.ImageIcon;
+import javax.swing.JTable;
+import javax.swing.table.TableModel;
+
+import docking.ActionContext;
+import docking.action.*;
+import ghidra.app.events.ProgramSelectionPluginEvent;
+import ghidra.framework.plugintool.Plugin;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramSelection;
+import resources.*;
+
+
+
+/**
+ * An action for creating a {@link ProgramSelection} from rows of an {@link AddressRangeTableModel}
+ * using either the min addresses or the max addresses
+ */
+public class SelectEndpointsAction extends DockingAction {
+ private Program program;
+ private AddressRangeTableModel model;
+ private Plugin plugin;
+ private RangeEndpoint endpoint;
+
+ enum RangeEndpoint {
+ MIN, MAX
+ }
+
+ /**
+ * Creates an action which selects the endpoint of a range based on {@code RangeEndpoint}
+ * @param plugin plugin
+ * @param program program
+ * @param model model
+ * @param endpoint left or right endpoint
+ */
+ public SelectEndpointsAction(Plugin plugin, Program program,
+ AddressRangeTableModel model, RangeEndpoint endpoint) {
+ super("Select " + endpoint.name(), plugin.getName());
+ this.program = program;
+ this.model = model;
+ this.plugin = plugin;
+ this.endpoint = endpoint;
+ init();
+ }
+
+ @Override
+ public boolean isAddToPopup(ActionContext context) {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ return !getSelectedRanges(context).isEmpty();
+ }
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ AddressSet selection = new AddressSet();
+ for (AddressRangeInfo rangeInfo : model.getLastSelectedObjects()) {
+ if (endpoint.equals(RangeEndpoint.MIN)) {
+ selection.add(rangeInfo.min());
+ }
+ else {
+ selection.add(rangeInfo.max());
+ }
+ }
+ plugin.getTool()
+ .firePluginEvent(new ProgramSelectionPluginEvent(plugin.getName(),
+ new ProgramSelection(selection), program));
+ }
+
+ private void init() {
+ ImageIcon icon = null;
+ String menuText = null;
+ String description = null;
+ int height = Icons.MAKE_SELECTION_ICON.getIconHeight()/2;
+ int weight = Icons.MAKE_SELECTION_ICON.getIconWidth()/2;
+ MultiIconBuilder iconBuilder = new MultiIconBuilder(Icons.MAKE_SELECTION_ICON);
+ if (endpoint.equals(RangeEndpoint.MIN)) {
+ iconBuilder.addIcon(Icons.UP_ICON, weight, height, QUADRANT.UL);
+ icon = iconBuilder.build();
+ menuText = "Select Min Endpoints";
+ description =
+ "Makes a Program Selection from the minimum addresses in the selected rows";
+ }
+ else {
+ iconBuilder.addIcon(Icons.DOWN_ICON, weight, height, QUADRANT.LL);
+ icon = iconBuilder.build();
+ menuText = "Select Max Endpoints";
+ description =
+ "Makes a Program Selection from the maximum addresses in the selected rows";
+ }
+ setPopupMenuData(new MenuData(new String[] { menuText }, icon));
+ setToolBarData(new ToolBarData(icon));
+ setDescription(description);
+ }
+
+ private List getSelectedRanges(ActionContext context) {
+ Component component = context.getSourceComponent();
+ if (!(component instanceof JTable table)) {
+ return Collections.emptyList();
+ }
+
+ TableModel tableModel = table.getModel();
+ if (model != tableModel) {
+ return Collections.emptyList();
+ }
+
+ List ranges = new ArrayList<>();
+ for (int row : table.getSelectedRows()) {
+ ranges.add(model.getRowObject(row));
+ }
+
+ return ranges;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/AddressRangeEndpointSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/AddressRangeEndpointSettingsDefinition.java
new file mode 100644
index 0000000000..cc2001e946
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/AddressRangeEndpointSettingsDefinition.java
@@ -0,0 +1,122 @@
+/* ###
+ * 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.util.table.field;
+
+import ghidra.docking.settings.EnumSettingsDefinition;
+import ghidra.docking.settings.Settings;
+import ghidra.program.model.address.AddressRange;
+
+/**
+ * A class for selecting whether to use the min address or the max address of an
+ * {@link AddressRange} for address range table columns
+ */
+public class AddressRangeEndpointSettingsDefinition implements EnumSettingsDefinition {
+
+ private static final String ADDRESS_RANGE_ENDPOINT = "Address Range Endpoint";
+ private static final String ENDPOINT = "Endpoint";
+ public static final String BEGIN = "Begin";
+ public static final String END = "End";
+ private static final String[] CHOICES = { BEGIN, END };
+ public static final int BEGIN_CHOICE_INDEX = 0;
+ public static final int END_CHOICE_INDEX = 1;
+ private static final int DEFAULT = 0;
+ public static final AddressRangeEndpointSettingsDefinition DEF =
+ new AddressRangeEndpointSettingsDefinition();
+
+ private AddressRangeEndpointSettingsDefinition() {
+ //singleton class
+ }
+
+ @Override
+ public boolean hasValue(Settings setting) {
+ return setting.getValue(ADDRESS_RANGE_ENDPOINT) != null;
+ }
+
+ @Override
+ public String getValueString(Settings settings) {
+ return CHOICES[getChoice(settings)];
+ }
+
+ @Override
+ public String getName() {
+ return ENDPOINT;
+ }
+
+ @Override
+ public String getStorageKey() {
+ return ADDRESS_RANGE_ENDPOINT;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Selects the base address";
+ }
+
+ @Override
+ public void clear(Settings settings) {
+ settings.clearSetting(ADDRESS_RANGE_ENDPOINT);
+
+ }
+
+ @Override
+ public void copySetting(Settings srcSettings, Settings destSettings) {
+ Long l = srcSettings.getLong(ADDRESS_RANGE_ENDPOINT);
+ if (l == null) {
+ destSettings.clearSetting(ADDRESS_RANGE_ENDPOINT);
+ }
+ else {
+ destSettings.setLong(ADDRESS_RANGE_ENDPOINT, l);
+ }
+
+ }
+
+ @Override
+ public int getChoice(Settings settings) {
+ if (settings == null) {
+ return DEFAULT;
+ }
+ Long value = settings.getLong(ADDRESS_RANGE_ENDPOINT);
+ if (value == null || value < 0 || value >= CHOICES.length) {
+ return DEFAULT;
+ }
+ return value.intValue();
+ }
+
+ @Override
+ public void setChoice(Settings settings, int value) {
+ if (value < DEFAULT) {
+ settings.clearSetting(ADDRESS_RANGE_ENDPOINT);
+ }
+ else {
+ if (value > CHOICES.length) {
+ value = CHOICES.length;
+ }
+ settings.setLong(ADDRESS_RANGE_ENDPOINT, value);
+ }
+
+ }
+
+ @Override
+ public String getDisplayChoice(int value, Settings settings) {
+ return CHOICES[value];
+ }
+
+ @Override
+ public String[] getDisplayChoices(Settings settings) {
+ return CHOICES;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java
index e2704f8e1e..c815273328 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/BytesTableColumn.java
@@ -15,13 +15,6 @@
*/
package ghidra.util.table.field;
-import java.awt.Component;
-
-import javax.swing.JLabel;
-import javax.swing.JTable;
-import javax.swing.table.TableModel;
-
-import docking.widgets.table.GTableCellRenderingData;
import ghidra.docking.settings.*;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
@@ -30,8 +23,6 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.util.BytesFieldLocation;
import ghidra.program.util.ProgramLocation;
-import ghidra.util.StringFormat;
-import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
/**
@@ -49,98 +40,7 @@ public class BytesTableColumn extends ProgramLocationTableColumnExtensionPoint monospacedRenderer = new AbstractGColumnRenderer<>() {
- @Override
- protected void configureFont(JTable table, TableModel model, int column) {
- setFont(getFixedWidthFont());
- }
-
- private String formatBytes(Byte[] bytes, Settings settings) {
- boolean bigEndian = (ENDIANESS.getChoice(settings) != EndianSettingsDefinition.LITTLE);
-
- int startIx = 0;
- int endIx = bytes.length;
- int inc = 1;
- if (!bigEndian) {
- startIx = bytes.length - 1;
- endIx = -1;
- inc = -1;
- }
-
- int format = FORMAT.getChoice(settings);
- if (format == FormatSettingsDefinition.CHAR) {
- return bytesToString(bytes);
- }
-
- StringBuilder buffer = new StringBuilder();
- for (int i = startIx; i != endIx; i += inc) {
- if (buffer.length() != 0) {
- buffer.append(' ');
- }
- buffer.append(getByteString(bytes[i], format));
- }
- return buffer.toString();
- }
-
- private String bytesToString(Byte[] bytes) {
- StringBuilder buf = new StringBuilder();
- for (byte b : bytes) {
- char c = (char) (b & 0xff);
- if (c > 32 && c < 128) {
- buf.append((char) (b & 0xff));
- }
- else {
- buf.append('.');
- }
- }
- return buf.toString();
- }
-
- private String getByteString(Byte b, int format) {
-
- String val;
- switch (format) {
- case FormatSettingsDefinition.DECIMAL:
- val = Integer.toString(b);
- break;
- case FormatSettingsDefinition.BINARY:
- val = Integer.toBinaryString(b & 0x0ff);
- val = StringFormat.padIt(val, 8, (char) 0, true);
- break;
- case FormatSettingsDefinition.OCTAL:
- val = Integer.toOctalString(b & 0x0ff);
- val = StringFormat.padIt(val, 3, (char) 0, true);
- break;
- default:
- case FormatSettingsDefinition.HEX:
- val = Integer.toHexString(b & 0x0ff).toUpperCase();
- val = StringFormat.padIt(val, 2, (char) 0, true);
- break;
- }
- return val;
- }
-
- @Override
- public Component getTableCellRendererComponent(GTableCellRenderingData data) {
-
- JLabel label = (JLabel) super.getTableCellRendererComponent(data);
-
- Object value = data.getValue();
- Settings settings = data.getColumnSettings();
-
- Byte[] bytes = (Byte[]) value;
-
- setText(formatBytes(bytes, settings));
-
- return label;
- }
-
- @Override
- public String getFilterString(Byte[] t, Settings settings) {
- String formatted = formatBytes(t, settings);
- return formatted;
- }
- };
+ private final GColumnRenderer monospacedRenderer = new MonospacedByteRenderer();
/**
* Default Constructor
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MonospacedByteRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MonospacedByteRenderer.java
new file mode 100644
index 0000000000..92299dd144
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MonospacedByteRenderer.java
@@ -0,0 +1,123 @@
+/* ###
+ * 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.util.table.field;
+
+import java.awt.Component;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableModel;
+
+import docking.widgets.table.GTableCellRenderingData;
+import ghidra.docking.settings.FormatSettingsDefinition;
+import ghidra.docking.settings.Settings;
+import ghidra.program.model.data.EndianSettingsDefinition;
+import ghidra.util.StringFormat;
+import ghidra.util.table.column.AbstractGColumnRenderer;
+
+public class MonospacedByteRenderer extends AbstractGColumnRenderer {
+ @Override
+ protected void configureFont(JTable table, TableModel model, int column) {
+ setFont(getFixedWidthFont());
+ }
+
+ private String formatBytes(Byte[] bytes, Settings settings) {
+ boolean bigEndian = (EndianSettingsDefinition.DEF
+ .getChoice(settings) != EndianSettingsDefinition.LITTLE);
+
+ int startIx = 0;
+ int endIx = bytes.length;
+ int inc = 1;
+ if (!bigEndian) {
+ startIx = bytes.length - 1;
+ endIx = -1;
+ inc = -1;
+ }
+
+ int format = FormatSettingsDefinition.DEF.getChoice(settings);
+ if (format == FormatSettingsDefinition.CHAR) {
+ return bytesToString(bytes);
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ for (int i = startIx; i != endIx; i += inc) {
+ if (buffer.length() != 0) {
+ buffer.append(' ');
+ }
+ buffer.append(getByteString(bytes[i], format));
+ }
+ return buffer.toString();
+ }
+
+ private String bytesToString(Byte[] bytes) {
+ StringBuilder buf = new StringBuilder();
+ for (byte b : bytes) {
+ char c = (char) (b & 0xff);
+ if (c > 32 && c < 128) {
+ buf.append((char) (b & 0xff));
+ }
+ else {
+ buf.append('.');
+ }
+ }
+ return buf.toString();
+ }
+
+ private String getByteString(Byte b, int format) {
+
+ String val;
+ switch (format) {
+ case FormatSettingsDefinition.DECIMAL:
+ val = Integer.toString(b);
+ break;
+ case FormatSettingsDefinition.BINARY:
+ val = Integer.toBinaryString(b & 0x0ff);
+ val = StringFormat.padIt(val, 8, (char) 0, true);
+ break;
+ case FormatSettingsDefinition.OCTAL:
+ val = Integer.toOctalString(b & 0x0ff);
+ val = StringFormat.padIt(val, 3, (char) 0, true);
+ break;
+ default:
+ case FormatSettingsDefinition.HEX:
+ val = Integer.toHexString(b & 0x0ff).toUpperCase();
+ val = StringFormat.padIt(val, 2, (char) 0, true);
+ break;
+ }
+ return val;
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(GTableCellRenderingData data) {
+
+ JLabel label = (JLabel) super.getTableCellRendererComponent(data);
+
+ Object value = data.getValue();
+ Settings settings = data.getColumnSettings();
+
+ Byte[] bytes = (Byte[]) value;
+
+ setText(formatBytes(bytes, settings));
+
+ return label;
+ }
+
+ @Override
+ public String getFilterString(Byte[] t, Settings settings) {
+ String formatted = formatBytes(t, settings);
+ return formatted;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/resources/ghidra/app/plugin/core/totd/tips.txt b/Ghidra/Features/Base/src/main/resources/ghidra/app/plugin/core/totd/tips.txt
index 70e5bac513..94b4b70295 100644
--- a/Ghidra/Features/Base/src/main/resources/ghidra/app/plugin/core/totd/tips.txt
+++ b/Ghidra/Features/Base/src/main/resources/ghidra/app/plugin/core/totd/tips.txt
@@ -102,4 +102,6 @@ You can quickly change the font size of the Listing, Decompiler or the Bytes win
You can quickly control the font size from the Theme Editor dialog via Edit->Theme->Configure.
+You can create a table whose rows correspond to the address ranges in a selection via Select->Create Table From Ranges.
+
This is the last tip. You can turn them off now.
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java
index 586169f31e..4c6abdef45 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserTest.java
@@ -35,6 +35,7 @@ import docking.widgets.table.GTable;
import generic.test.TestUtils;
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.events.ProgramSelectionPluginEvent;
+import ghidra.app.plugin.core.codebrowser.SelectEndpointsAction.RangeEndpoint;
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
import ghidra.app.plugin.core.symtable.SymbolTablePlugin;
import ghidra.app.plugin.core.table.TableComponentProvider;
@@ -49,7 +50,9 @@ import ghidra.program.util.InteriorSelection;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
+import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressPreviewTableModel;
+import ghidra.util.task.TaskMonitor;
public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
@@ -168,7 +171,7 @@ public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
ListSelectionModel selectionModel = table.getSelectionModel();
// select all the rows
- selectionModel.setSelectionInterval(0, table.getRowCount() - 1);
+ runSwing(() -> selectionModel.setSelectionInterval(0, table.getRowCount() - 1));
AddressPreviewTableModel addressModel = (AddressPreviewTableModel) tableProvider.getModel();
// get a selection from the model and make sure it matches the original selection
@@ -182,6 +185,54 @@ public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
}
}
+ @Test
+ public void testAddressRangeTableFromSelection() throws CancelledException {
+ AddressSet undefined = new AddressSet(program.getListing()
+ .getUndefinedRanges(program.getMemory().getAllInitializedAddressSet(), true,
+ TaskMonitor.DUMMY));
+ setSelection(undefined);
+ DockingActionIf createTableAction =
+ getAction(tool, CodeBrowserSelectionPlugin.CREATE_ADDRESS_RANGE_TABLE_ACTION_NAME);
+ performAction(createTableAction, true);
+ TableComponentProvider> tableProvider =
+ waitForComponentProvider(tool.getToolFrame(), TableComponentProvider.class, 2000);
+ Object threadedPanel = TestUtils.getInstanceField("threadedPanel", tableProvider);
+ GTable table = (GTable) TestUtils.getInstanceField("table", threadedPanel);
+ assertEquals(7, table.getRowCount());
+ ListSelectionModel selectionModel = table.getSelectionModel();
+ // select all the rows
+ runSwing(() -> selectionModel.setSelectionInterval(0, table.getRowCount() - 1));
+ AddressRangeTableModel addressModel = (AddressRangeTableModel) tableProvider.getModel();
+
+ // get a selection from the model and make sure it matches the original selection
+ ProgramSelection tableProgramSelection =
+ addressModel.getProgramSelection(table.getSelectedRows());
+
+ AddressSet fromTable = new AddressSet();
+ tableProgramSelection.getAddressRanges().forEach(r -> fromTable.add(r));
+ assertEquals(fromTable, undefined);
+
+ DockingActionIf selectMinsAction =
+ getAction(tool, "Select " + RangeEndpoint.MIN.name());
+ assertNotNull(selectMinsAction);
+ performAction(selectMinsAction, true);
+ ProgramSelection minSelect = getCurrentSelection();
+ assertEquals(7, minSelect.getNumAddresses());
+ for (AddressRange range : fromTable.getAddressRanges()) {
+ assertTrue(minSelect.contains(range.getMinAddress()));
+ }
+
+ DockingActionIf selectMaxesAction =
+ getAction(tool, "Select " + RangeEndpoint.MAX.name());
+ assertNotNull(selectMaxesAction);
+ performAction(selectMaxesAction, true);
+ ProgramSelection maxSelect = getCurrentSelection();
+ assertEquals(7, maxSelect.getNumAddresses());
+ for (AddressRange range : fromTable.getAddressRanges()) {
+ assertTrue(maxSelect.contains(range.getMaxAddress()));
+ }
+ }
+
@Test
public void testSimpleDragToCreateSelection() {
ProgramSelection ps = dragToMakeSelection();