mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-2297 added action to create a table based on address ranges in a selection
This commit is contained in:
@@ -396,4 +396,4 @@ icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOu
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Dark Defaults]
|
[Dark Defaults]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</HEAD>
|
</HEAD>
|
||||||
|
|
||||||
<BODY>
|
<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
|
<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
|
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>
|
"help/shared/arrow.gif">Create Table From Selection</B> from the tool's menu bar.</P>
|
||||||
</BLOCKQUOTE>
|
</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="providedbyplugin">Provided by: <I>Code Browser</I> plugin</P>
|
||||||
|
|
||||||
<P class="relatedtopic">Related Topics:</P>
|
<P class="relatedtopic">Related Topics:</P>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,13 +20,14 @@ import javax.swing.Icon;
|
|||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.GhidraOptions;
|
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
import ghidra.app.plugin.core.codebrowser.SelectEndpointsAction.RangeEndpoint;
|
||||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||||
import ghidra.app.util.HelpTopics;
|
import ghidra.app.util.HelpTopics;
|
||||||
import ghidra.app.util.SearchConstants;
|
import ghidra.app.util.SearchConstants;
|
||||||
import ghidra.app.util.query.TableService;
|
import ghidra.app.util.query.TableService;
|
||||||
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
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 SELECT_GROUP = "Select Group";
|
||||||
private static final String SELECTION_LIMIT_OPTION_NAME = "Table From Selection Limit";
|
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) {
|
public CodeBrowserSelectionPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
createActions();
|
createActions();
|
||||||
|
initializeOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createActions() {
|
private void createActions() {
|
||||||
@@ -98,11 +111,57 @@ public class CodeBrowserSelectionPlugin extends Plugin {
|
|||||||
new ActionBuilder("Create Table From Selection", getName())
|
new ActionBuilder("Create Table From Selection", getName())
|
||||||
.menuPath(ToolConstants.MENU_SELECTION, "Create Table From Selection")
|
.menuPath(ToolConstants.MENU_SELECTION, "Create Table From Selection")
|
||||||
.menuGroup("SelectUtils")
|
.menuGroup("SelectUtils")
|
||||||
.helpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table"))
|
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Selection_Tables"))
|
||||||
.withContext(CodeViewerActionContext.class, true)
|
.withContext(CodeViewerActionContext.class, true)
|
||||||
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
||||||
.onAction(c -> createTable((CodeViewerProvider) c.getComponentProvider()))
|
.onAction(c -> createTable((CodeViewerProvider) c.getComponentProvider()))
|
||||||
.buildAndInstall(tool);
|
.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)
|
public void load(Accumulator<Address> accumulator, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
|
||||||
ToolOptions options = tool.getOptions(ToolConstants.TOOL_OPTIONS);
|
ToolOptions options = tool.getOptions(OPTION_CATEGORY_NAME);
|
||||||
int resultsLimit = options.getInt(GhidraOptions.OPTION_SEARCH_LIMIT,
|
int resultsLimit = options.getInt(SELECTION_LIMIT_OPTION_NAME, SELECTION_LIMIT_DEFAULT);
|
||||||
SearchConstants.DEFAULT_SEARCH_LIMIT);
|
|
||||||
|
|
||||||
long size = selection.getNumAddresses();
|
long size = selection.getNumAddresses();
|
||||||
monitor.initialize(size);
|
monitor.initialize(size);
|
||||||
@@ -167,7 +225,8 @@ public class CodeBrowserSelectionPlugin extends Plugin {
|
|||||||
if (accumulator.size() >= resultsLimit) {
|
if (accumulator.size() >= resultsLimit) {
|
||||||
Msg.showWarn(this, null, "Results Truncated",
|
Msg.showWarn(this, null, "Results Truncated",
|
||||||
"Results are limited to " + resultsLimit + " code units.\n" +
|
"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 +
|
SELECTION_LIMIT_OPTION_NAME +
|
||||||
"\".");
|
"\".");
|
||||||
break;
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,13 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.util.table.field;
|
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.docking.settings.*;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
@@ -30,8 +23,6 @@ import ghidra.program.model.listing.*;
|
|||||||
import ghidra.program.model.mem.MemoryAccessException;
|
import ghidra.program.model.mem.MemoryAccessException;
|
||||||
import ghidra.program.util.BytesFieldLocation;
|
import ghidra.program.util.BytesFieldLocation;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.util.StringFormat;
|
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
|
||||||
import ghidra.util.table.column.GColumnRenderer;
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,98 +40,7 @@ public class BytesTableColumn extends ProgramLocationTableColumnExtensionPoint<A
|
|||||||
private static SettingsDefinition[] SETTINGS_DEFS =
|
private static SettingsDefinition[] SETTINGS_DEFS =
|
||||||
{ BYTE_COUNT, MEMORY_OFFSET, ENDIANESS, FORMAT };
|
{ BYTE_COUNT, MEMORY_OFFSET, ENDIANESS, FORMAT };
|
||||||
|
|
||||||
private final GColumnRenderer<Byte[]> monospacedRenderer = new AbstractGColumnRenderer<>() {
|
private final GColumnRenderer<Byte[]> monospacedRenderer = new MonospacedByteRenderer();
|
||||||
@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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Constructor
|
* Default Constructor
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 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.
|
This is the last tip. You can turn them off now.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import docking.widgets.table.GTable;
|
|||||||
import generic.test.TestUtils;
|
import generic.test.TestUtils;
|
||||||
import ghidra.app.cmd.data.CreateDataCmd;
|
import ghidra.app.cmd.data.CreateDataCmd;
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
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.navigation.NextPrevAddressPlugin;
|
||||||
import ghidra.app.plugin.core.symtable.SymbolTablePlugin;
|
import ghidra.app.plugin.core.symtable.SymbolTablePlugin;
|
||||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||||
@@ -49,7 +50,9 @@ import ghidra.program.util.InteriorSelection;
|
|||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.table.AddressPreviewTableModel;
|
import ghidra.util.table.AddressPreviewTableModel;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
|
public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
private TestEnv env;
|
private TestEnv env;
|
||||||
@@ -168,7 +171,7 @@ public class CodeBrowserTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
ListSelectionModel selectionModel = table.getSelectionModel();
|
ListSelectionModel selectionModel = table.getSelectionModel();
|
||||||
|
|
||||||
// select all the rows
|
// select all the rows
|
||||||
selectionModel.setSelectionInterval(0, table.getRowCount() - 1);
|
runSwing(() -> selectionModel.setSelectionInterval(0, table.getRowCount() - 1));
|
||||||
AddressPreviewTableModel addressModel = (AddressPreviewTableModel) tableProvider.getModel();
|
AddressPreviewTableModel addressModel = (AddressPreviewTableModel) tableProvider.getModel();
|
||||||
|
|
||||||
// get a selection from the model and make sure it matches the original selection
|
// 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
|
@Test
|
||||||
public void testSimpleDragToCreateSelection() {
|
public void testSimpleDragToCreateSelection() {
|
||||||
ProgramSelection ps = dragToMakeSelection();
|
ProgramSelection ps = dragToMakeSelection();
|
||||||
|
|||||||
Reference in New Issue
Block a user