mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-18 16:40:08 +00:00
Merge remote-tracking branch 'origin/GP-2297_James_address_range_table--SQUASHED'
This commit is contained in:
commit
0cc48f1aca
@ -398,4 +398,4 @@ icon.base.util.datatree.version.control.archive.dt.checkout.undo = vcUndoCheckOu
|
||||
|
||||
|
||||
|
||||
[Dark Defaults]
|
||||
[Dark Defaults]
|
||||
|
@ -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>
|
||||
|
@ -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.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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
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
|
||||
|
@ -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 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.
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user