Merge remote-tracking branch 'origin/GP-2297_James_address_range_table--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-10-16 10:02:57 -04:00
commit 0cc48f1aca
13 changed files with 1101 additions and 110 deletions

View File

@ -398,4 +398,4 @@ icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOu
[Dark Defaults]
[Dark Defaults]

View File

@ -13,7 +13,7 @@
</HEAD>
<BODY>
<H1><A name="Selection_Table"></A>Selection Table</H1>
<H1><A name="Selection_Tables"></A>Selection Tables</H1>
<P>The <B>Create Table From Selection</B> action will create a table of addresses based upon
the current selection in the <A href="CodeBrowser.htm#Code_Browser">Listing</A>. Each entry in
@ -30,6 +30,25 @@
"help/shared/arrow.gif">Create Table From Selection</B> from the tool's menu bar.</P>
</BLOCKQUOTE>
<P> The <B>Create Table From Ranges</B> action will create a table based on the
current selection. Each row of the table corresponds to an address range in the selection.</P>
<H3><A name="Range_Table_Actions"></A>Range Table Actions</H3>
<P>There are two special actions available on an address range table:
<UL>
<LI>
<B> Select Minimum Addresses</B>
<P> This action creates a program selection containing the minimum address of each selected
address range.</P>
</LI>
<LI>
<B> Select Maximum Addresses</B>
<P> This action creates a program selection containing the maximum address of each selected
address range.</P>
</LI>
</UL>
</P>
<P class="providedbyplugin">Provided by: <I>Code Browser</I> plugin</P>
<P class="relatedtopic">Related Topics:</P>

View File

@ -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<AddressRangeInfo, Byte[], Program> {
private final GColumnRenderer<Byte[]> 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<Byte[]> 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();
}
}

View File

@ -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<AddressRangeInfo, CodeUnitTableCellData, Program> {
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<CodeUnitTableCellData> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<AddressRangeInfo> {
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<AddressRangeInfo> 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<AddressRangeInfo> createTableColumnDescriptor() {
TableColumnDescriptor<AddressRangeInfo> 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<AddressRangeInfo, Address, Object> {
@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<AddressRangeInfo, Address, Object> {
@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<AddressRangeInfo, Long, Object> {
@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<AddressRangeInfo, Boolean, Object> {
@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<AddressRangeInfo, String, Object> {
@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<AddressRangeInfo, Integer, Object> {
@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<AddressRangeInfo, Integer, Object> {
@Override
public String getColumnName() {
return "From References";
}
@Override
public Integer getValue(AddressRangeInfo rangeInfo, Settings settings, Object data,
ServiceProvider services) throws IllegalArgumentException {
return rangeInfo.numRefsFrom();
}
}
}

View File

@ -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<AddressRangeInfo> 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<Address> 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");
}
}

View File

@ -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<AddressRangeInfo> 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<AddressRangeInfo> ranges = new ArrayList<>();
for (int row : table.getSelectedRows()) {
ranges.add(model.getRowObject(row));
}
return ranges;
}
}

View File

@ -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;
}
}

View File

@ -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<A
private static SettingsDefinition[] SETTINGS_DEFS =
{ BYTE_COUNT, MEMORY_OFFSET, ENDIANESS, FORMAT };
private final GColumnRenderer<Byte[]> 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<Byte[]> monospacedRenderer = new MonospacedByteRenderer();
/**
* Default Constructor

View File

@ -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<Byte[]> {
@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;
}
}

View File

@ -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.

View File

@ -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();