GP-4855: Adding new fields and copy specials for various types of address offsets

This commit is contained in:
Ryan Kurtz 2024-08-22 07:46:05 -04:00
parent 2c3a815163
commit fb6f853392
14 changed files with 647 additions and 409 deletions

View File

@ -71,7 +71,7 @@ color.fg.listing.block.start = color.palette.indigo
color.fg.listing.bytes.alignment = color.palette.gray
color.fg.listing.bytes.entropy = color.fg
color.fg.listing.disassembly.external = color.fg
color.fg.listing.file.offset = color.fg
color.fg.listing.field.offset = color.fg
color.fg.listing.instruction.parallel = color.palette.blue
color.fg.listing.mask.bits = color.palette.navy

View File

@ -584,11 +584,11 @@
<BLOCKQUOTE>
<P>The File Offset field shows the filename and file offset of the original imported byte
value for the given address. If the address's byte was not derived from an imported binary
file, or file offset tracking is not supported by the binary file's importer, a value of
"N/A" is shown.</P>
<P><B>Show Filename -</B> Option to prefix the file offset with the source filename. This
file, or file offset tracking is not supported by the binary file's importer, no offset is
shown.</P>
<P><B>Show Name -</B> Option to prefix the file offset with the source filename. This
is useful if more than one binary file has been imported into a program.</P>
<P><B>Show Numbers In Hex -</B> Option to display the file offset in hexadecimal rather than
<P><B>Use Hex -</B> Option to display the file offset in hexadecimal rather than
decimal.</P>
<P><IMG src="help/shared/note.png" border="0">The File Offset field is disabled by default.
To enable the field, see the <A HREF="Browser_Field_Formatter.htm#Enable_Field">Enable Field
@ -596,6 +596,52 @@
</P>
</BLOCKQUOTE>
<H3><A name="Function_Offset_Field"></A>Function Offset Field</H3>
<BLOCKQUOTE>
<P>The Function Offset field shows the function name and function offset of the
the given address. If the address is not in a function, no offset is shown.</P>
<P><B>Show Name -</B> Option to prefix the function offset with the function name.</P>
<P><B>Use Hex -</B> Option to display the function offset in hexadecimal rather than
decimal.</P>
<P><IMG src="help/shared/note.png" border="0">The Function Offset field is disabled by
default. To enable the field, see the
<A HREF="Browser_Field_Formatter.htm#Enable_Field">Enable Field</A> section.
</P>
</BLOCKQUOTE>
<H3><A name="Imagebase_Offset_Field"></A>Imagebase Offset Field</H3>
<BLOCKQUOTE>
<P>The Imagebase Offset field shows the imagebase offset of the given address. If the
address and the imagebase are in different address spaces, no offset is shown.</P>
<P><B>Show Name -</B> Option to prefix the imagebase offset with "imagebase".</P>
<P><B>Use Hex -</B> Option to display the imagebase offset in hexadecimal rather than
decimal.</P>
<P><IMG src="help/shared/note.png" border="0">The Imagebase Offset field is disabled by
default. To enable the field, see the
<A HREF="Browser_Field_Formatter.htm#Enable_Field">Enable Field</A> section.
</P>
</BLOCKQUOTE>
<H3><A name="MemoryBlock_Offset_Field"></A>MemoryBlock Offset Field</H3>
<BLOCKQUOTE>
<P>The MemoryBlock Offset field shows the memory block name and memory block offset of the
given address.</P>
<P><B>Show Name -</B> Option to prefix the memory block offset with the memory block
name.</P>
<P><B>Use Hex -</B> Option to display the memory block offset in hexadecimal rather than
decimal.</P>
<P><IMG src="help/shared/note.png" border="0">The MemoryBlock Offset field is disabled by
default. To enable the field, see the
<A HREF="Browser_Field_Formatter.htm#Enable_Field">Enable Field</A> section.
</P>
</BLOCKQUOTE>
<H3><A name="Format_Code"></A>Format Code</H3>

View File

@ -4,9 +4,9 @@
* 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.
@ -42,7 +42,6 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext;
import ghidra.app.services.ClipboardContentProviderService;
import ghidra.app.util.*;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.mem.AddressSourceInfo;
@ -51,6 +50,7 @@ import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.Msg;
@ -70,6 +70,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier
new ClipboardType(DataFlavor.stringFlavor, "Address w/ Offset");
public static final ClipboardType BYTE_SOURCE_OFFSET_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Byte Source Offset");
public static final ClipboardType FUNCTION_OFFSET_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Function Offset");
public static final ClipboardType IMAGEBASE_OFFSET_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Imagebase Offset");
public static final ClipboardType BLOCK_OFFSET_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Memory Block Offset");
public static final ClipboardType CODE_TEXT_TYPE =
new ClipboardType(DataFlavor.stringFlavor, "Formatted Code");
public static final ClipboardType LABELS_COMMENTS_TYPE =
@ -100,6 +106,9 @@ public class CodeBrowserClipboardProvider extends ByteCopier
list.add(ADDRESS_TEXT_TYPE);
list.add(ADDRESS_TEXT_WITH_OFFSET_TYPE);
list.add(BYTE_SOURCE_OFFSET_TYPE);
list.add(BLOCK_OFFSET_TYPE);
list.add(FUNCTION_OFFSET_TYPE);
list.add(IMAGEBASE_OFFSET_TYPE);
return list;
}
@ -230,6 +239,15 @@ public class CodeBrowserClipboardProvider extends ByteCopier
else if (copyType == BYTE_SOURCE_OFFSET_TYPE) {
return copyByteSourceOffset(monitor);
}
else if (copyType == BLOCK_OFFSET_TYPE) {
return copyBlockSourceOffset(monitor);
}
else if (copyType == FUNCTION_OFFSET_TYPE) {
return copyFunctionSourceOffset(monitor);
}
else if (copyType == IMAGEBASE_OFFSET_TYPE) {
return copyImagebaseSourceOffset(monitor);
}
else if (copyType == CODE_TEXT_TYPE) {
return copyCode(monitor);
}
@ -409,6 +427,48 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return createStringTransferable(String.join("\n", strings));
}
private Transferable copyBlockSourceOffset(TaskMonitor monitor) {
AddressSetView addrs = getSelectedAddresses();
Memory mem = currentProgram.getMemory();
List<String> strings = new ArrayList<>();
AddressIterator addresses = addrs.getAddresses(true);
while (addresses.hasNext() && !monitor.isCancelled()) {
Address addr = addresses.next();
MemoryBlock block = mem.getBlock(addr);
strings.add(
block != null ? "%x".formatted(addr.subtract(block.getStart())) : "<NO_OFFSET>");
}
return createStringTransferable(String.join("\n", strings));
}
private Transferable copyFunctionSourceOffset(TaskMonitor monitor) {
AddressSetView addrs = getSelectedAddresses();
Listing listing = currentProgram.getListing();
List<String> strings = new ArrayList<>();
AddressIterator addresses = addrs.getAddresses(true);
while (addresses.hasNext() && !monitor.isCancelled()) {
Address addr = addresses.next();
Function function = listing.getFunctionContaining(addr);
strings.add(function != null ? "%x".formatted(addr.subtract(function.getEntryPoint()))
: "<NO_OFFSET>");
}
return createStringTransferable(String.join("\n", strings));
}
private Transferable copyImagebaseSourceOffset(TaskMonitor monitor) {
AddressSetView addrs = getSelectedAddresses();
List<String> strings = new ArrayList<>();
AddressIterator addresses = addrs.getAddresses(true);
while (addresses.hasNext() && !monitor.isCancelled()) {
Address addr = addresses.next();
Address imagebase = currentProgram.getImageBase();
strings.add(
addr.hasSameAddressSpace(imagebase) ? "%x".formatted(addr.subtract(imagebase))
: "<NO_OFFSET>");
}
return createStringTransferable(String.join("\n", strings));
}
protected Transferable copyCode(TaskMonitor monitor) {
AddressSetView addressSet = getSelectedAddresses();
@ -480,8 +540,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
List<?> list =
(List<?>) pasteData.getTransferData(CodeUnitInfoTransferable.localDataTypeFlavor);
List<CodeUnitInfo> infos = CollectionUtils.asList(list, CodeUnitInfo.class);
Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos, pasteLabels,
pasteComments);
CodeUnitInfoPasteCmd cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos,
pasteLabels, pasteComments);
return tool.execute(cmd, currentProgram);
}
catch (Exception e) {

View File

@ -0,0 +1,185 @@
/* ###
* 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.util.viewer.field;
import java.math.BigInteger;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GColor;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Data;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
/**
* Generates Offset fields
*/
public abstract class AbstractOffsetFieldFactory extends FieldFactory {
public static final GColor COLOR = new GColor("color.fg.listing.field.offset");
private static final String SHOW_NAME = "Show Name";
private static final String USE_HEX = "Use Hex";
private static final boolean DEFAULT_SHOW_NAME = false;
private static final boolean DEFAULT_USE_HEX = true;
protected boolean showName;
protected boolean useHex;
protected String fieldName;
protected String groupTitle;
/**
* Creates a new {@link AbstractOffsetFieldFactory}
*
* @param offsetDescription A description of the offset
*/
public AbstractOffsetFieldFactory(String offsetDescription) {
super(offsetDescription + " Offset");
}
/**
* Creates a new {@link AbstractOffsetFieldFactory}
*
* @param offsetDescription A description of the field offset
* @param nameDescription A description of the name that can get prepended to the field offset
* @param model the model that the field belongs to.
* @param hlProvider the HightLightStringProvider.
* @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties.
*/
protected AbstractOffsetFieldFactory(String offsetDescription, String nameDescription,
FieldFormatModel model, ListingHighlightProvider hlProvider, Options displayOptions,
Options fieldOptions) {
super(offsetDescription + " Offset", model, hlProvider, displayOptions, fieldOptions);
fieldName = offsetDescription + " Offset";
groupTitle = offsetDescription + " Offset Field";
initOptions(fieldOptions, offsetDescription, nameDescription);
}
/**
* Gets the offset value
*
* @param codeUnit The {@link CodeUnit}
* @return The offset value
*/
public abstract String getOffsetValue(CodeUnit codeUnit);
/**
* Gets the {@link OffsetFieldType offset type}
*
* @return the {@link OffsetFieldType offset type}
*/
public abstract OffsetFieldType getOffsetFieldType();
private void initOptions(Options fieldOptions, String offsetDescription,
String nameDescription) {
HelpLocation helpLoc =
new HelpLocation("CodeBrowserPlugin", offsetDescription + "_Offset_Field");
fieldOptions.getOptions(groupTitle).setOptionsHelpLocation(helpLoc);
fieldOptions.registerOption(getFullOptionName(SHOW_NAME), DEFAULT_SHOW_NAME, helpLoc,
"Prepends the %s name to the %s offset in the offset field."
.formatted(nameDescription.toLowerCase(), offsetDescription));
fieldOptions.registerOption(getFullOptionName(USE_HEX), DEFAULT_USE_HEX, helpLoc,
"Toggles displaying offsets in hexadecimal/decimal in the offset field.");
showName = fieldOptions.getBoolean(getFullOptionName(SHOW_NAME), DEFAULT_SHOW_NAME);
useHex = fieldOptions.getBoolean(getFullOptionName(USE_HEX), DEFAULT_USE_HEX);
}
@Override
public ListingField getField(ProxyObj<?> proxy, int varWidth) {
Object obj = proxy.getObject();
if (!enabled || !(obj instanceof CodeUnit)) {
return null;
}
FieldElement fieldElement = new TextFieldElement(
new AttributedString(getOffsetValue((CodeUnit) obj), COLOR, getMetrics()), 0, 0);
ListingTextField listingTextField = ListingTextField.createSingleLineTextField(this, proxy,
fieldElement, startX + varWidth, width, hlProvider);
listingTextField.setPrimary(true);
return listingTextField;
}
@Override
public void fieldOptionsChanged(Options options, String optionsName, Object oldValue,
Object newValue) {
if (optionsName.equals(getFullOptionName(SHOW_NAME))) {
showName = ((Boolean) newValue).booleanValue();
model.update();
}
else if (optionsName.equals(getFullOptionName(USE_HEX))) {
useHex = ((Boolean) newValue).booleanValue();
model.update();
}
}
@Override
public FieldLocation getFieldLocation(ListingField lf, BigInteger index, int fieldNum,
ProgramLocation loc) {
if (loc instanceof OffsetFieldLocation offsetLoc &&
offsetLoc.getType().equals(getOffsetFieldType())) {
Object obj = lf.getProxy().getObject();
if (obj instanceof CodeUnit && hasSamePath(lf, offsetLoc)) {
return new FieldLocation(index, fieldNum, 0, offsetLoc.getCharOffset());
}
}
return null;
}
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField lf) {
Object obj = lf.getProxy().getObject();
if (!(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
Address addr = cu.getMinAddress();
int[] cpath = null;
if (cu instanceof Data) {
cpath = ((Data) cu).getComponentPath();
}
return new OffsetFieldLocation(cu.getProgram(), addr, cpath, col, getOffsetFieldType());
}
@Override
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
if (!CodeUnit.class.isAssignableFrom(proxyObjectClass)) {
return false;
}
return (category == FieldFormatModel.INSTRUCTION_OR_DATA ||
category == FieldFormatModel.OPEN_DATA || category == FieldFormatModel.ARRAY);
}
/**
* Gets the full option name, which includes the group and options delimiter
*
* @param shortName The short option name (no group or options delimiter)
* @return The full option name, which includes the group and options delimiter
*/
private String getFullOptionName(String shortName) {
return groupTitle + Options.DELIMITER + shortName;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -15,82 +15,44 @@
*/
package ghidra.app.util.viewer.field;
import java.math.BigInteger;
import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GColor;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.framework.options.*;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Data;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.mem.MemoryBlockSourceInfo;
import ghidra.program.util.FileOffsetFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.exception.AssertException;
import ghidra.program.util.OffsetFieldType;
/**
* Generates File Offset fields
* Generates Function Offset fields
*/
public class FileOffsetFieldFactory extends FieldFactory {
public class FileOffsetFieldFactory extends AbstractOffsetFieldFactory {
public static final String FIELD_NAME = "File Offset";
public static final GColor COLOR = new GColor("color.fg.listing.file.offset");
public static final String GROUP_TITLE = "File Offset Field";
public final static String FILE_OFFSET_DISPLAY_OPTIONS_NAME =
GROUP_TITLE + Options.DELIMITER + "File Offset Display Options";
private boolean showFilename;
private boolean useHex;
private static final String FIELD_OFFSET_DESCRIPTION = "File";
private static final String FIELD_NAME_DESCRIPTION = "File";
/**
* Default Constructor
* Creates a new default {@link FileOffsetFieldFactory}
*/
public FileOffsetFieldFactory() {
super(FIELD_NAME);
super(FIELD_OFFSET_DESCRIPTION);
}
/**
* Constructor
* @param model the model that the field belongs to.
* @param hlProvider the HightLightStringProvider.
* @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties.
* Creates a new {@link FileOffsetFieldFactory}
*
* @param model the {@link FieldFormatModel} that the field belongs to
* @param hlProvider the {@link ListingHighlightProvider}
* @param displayOptions the {@link Options} for display properties
* @param fieldOptions the {@link Options} for field specific properties
*/
private FileOffsetFieldFactory(FieldFormatModel model, ListingHighlightProvider hlProvider,
Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions);
initOptions(fieldOptions);
}
private void initOptions(Options fieldOptions) {
HelpLocation helpLoc = new HelpLocation("CodeBrowserPlugin", "File_Offset_Field");
fieldOptions.registerOption(FILE_OFFSET_DISPLAY_OPTIONS_NAME, OptionType.CUSTOM_TYPE,
new FileOffsetFieldOptionsWrappedOption(), helpLoc,
"Adjusts the File Offset Field display",
() -> new FileOffsetFieldOptionsPropertyEditor());
CustomOption customOption =
fieldOptions.getCustomOption(FILE_OFFSET_DISPLAY_OPTIONS_NAME, null);
if (!(customOption instanceof FileOffsetFieldOptionsWrappedOption)) {
throw new AssertException("Someone set an option for " +
FILE_OFFSET_DISPLAY_OPTIONS_NAME + " that is not the expected " +
FileOffsetFieldOptionsWrappedOption.class.getName() + " type.");
}
FileOffsetFieldOptionsWrappedOption fofowo =
(FileOffsetFieldOptionsWrappedOption) customOption;
showFilename = fofowo.showFilename();
useHex = fofowo.useHex();
fieldOptions.getOptions(GROUP_TITLE).setOptionsHelpLocation(helpLoc);
super(FIELD_OFFSET_DESCRIPTION, FIELD_NAME_DESCRIPTION, model, hlProvider, displayOptions,
fieldOptions);
}
@Override
@ -101,95 +63,28 @@ public class FileOffsetFieldFactory extends FieldFactory {
}
@Override
public void fieldOptionsChanged(Options options, String optionsName, Object oldValue,
Object newValue) {
if (optionsName.equals(FILE_OFFSET_DISPLAY_OPTIONS_NAME)) {
FileOffsetFieldOptionsWrappedOption fofowo =
(FileOffsetFieldOptionsWrappedOption) newValue;
showFilename = fofowo.showFilename();
useHex = fofowo.useHex();
model.update();
}
}
@Override
public ListingField getField(ProxyObj<?> proxy, int varWidth) {
Object obj = proxy.getObject();
if (!enabled || !(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
public String getOffsetValue(CodeUnit cu) {
Address addr = cu.getAddress();
MemoryBlock block = cu.getProgram().getMemory().getBlock(addr);
String text = "N/A";
String text = "";
for (MemoryBlockSourceInfo sourceInfo : block.getSourceInfos()) {
if (sourceInfo.contains(addr)) {
if (sourceInfo.getFileBytes().isPresent()) {
FileBytes fileBytes = sourceInfo.getFileBytes().get();
long offset = sourceInfo.getFileBytesOffset(addr);
if (useHex) {
text = String.format("0x%x", offset);
}
else {
text = String.format("%d", offset);
}
if (showFilename) {
text = fileBytes.getFilename() + ":" + text;
text = String.format(useHex ? "0x%x" : "%d", offset);
if (showName) {
text = "%s:%s".formatted(fileBytes.getFilename(), text);
}
break;
}
}
}
FieldElement fieldElement =
new TextFieldElement(new AttributedString(text, COLOR, getMetrics()), 0, 0);
ListingTextField listingTextField = ListingTextField.createSingleLineTextField(this, proxy,
fieldElement, startX + varWidth, width, hlProvider);
listingTextField.setPrimary(true);
return listingTextField;
return text;
}
@Override
public FieldLocation getFieldLocation(ListingField lf, BigInteger index, int fieldNum,
ProgramLocation loc) {
if (loc instanceof FileOffsetFieldLocation) {
FileOffsetFieldLocation fileOffsetFieldLoc = (FileOffsetFieldLocation) loc;
Object obj = lf.getProxy().getObject();
if (obj instanceof CodeUnit && hasSamePath(lf, fileOffsetFieldLoc)) {
return new FieldLocation(index, fieldNum, 0, fileOffsetFieldLoc.getCharOffset());
}
}
return null;
}
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField lf) {
Object obj = lf.getProxy().getObject();
if (!(obj instanceof CodeUnit)) {
return null;
}
CodeUnit cu = (CodeUnit) obj;
Address addr = cu.getMinAddress();
int[] cpath = null;
if (cu instanceof Data) {
cpath = ((Data) cu).getComponentPath();
}
return new FileOffsetFieldLocation(cu.getProgram(), addr, cpath, col);
}
@Override
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
if (!CodeUnit.class.isAssignableFrom(proxyObjectClass)) {
return false;
}
return (category == FieldFormatModel.INSTRUCTION_OR_DATA ||
category == FieldFormatModel.OPEN_DATA || category == FieldFormatModel.ARRAY);
public OffsetFieldType getOffsetFieldType() {
return OffsetFieldType.FILE;
}
}

View File

@ -1,136 +0,0 @@
/* ###
* 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.util.viewer.field;
import java.awt.Component;
import java.beans.PropertyEditorSupport;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.label.GDLabel;
import ghidra.framework.options.CustomOptionsEditor;
import ghidra.util.HTMLUtilities;
import ghidra.util.layout.PairLayout;
/**
* Provides a custom GUI layout for the File Offset field options
*/
public class FileOffsetFieldOptionsPropertyEditor extends PropertyEditorSupport
implements CustomOptionsEditor {
private static final String SHOW_FILENAME_LABEL = "Show Filename";
private static final String USE_HEX_LABEL = "Show Numbers In Hex";
private static final String[] NAMES = { SHOW_FILENAME_LABEL, USE_HEX_LABEL };
private static final String SHOW_FILENAME_TOOLTIP = HTMLUtilities.toWrappedHTML(
"Prepends the filename to the file offset in the File Offset field.", 75);
private static final String USE_HEX_TOOLTIP = HTMLUtilities.toWrappedHTML(
"Toggles displaying file offsets in hexadecimal/decimal in the File Offset field.", 75);
private static final String[] DESCRIPTIONS = { SHOW_FILENAME_TOOLTIP, USE_HEX_TOOLTIP };
private FileOffsetFieldOptionsWrappedOption option;
private Component editorComponent;
private GCheckBox showFilenameCheckbox;
private GCheckBox useHexCheckbox;
/**
* Creates a new {@link FileOffsetFieldOptionsPropertyEditor}
*/
public FileOffsetFieldOptionsPropertyEditor() {
editorComponent = buildEditor();
}
private Component buildEditor() {
// we want to have a panel with our options so that we may group them together
JPanel panel = new JPanel(new PairLayout(6, 10));
GDLabel showFilenameLabel = new GDLabel(SHOW_FILENAME_LABEL, SwingConstants.RIGHT);
showFilenameLabel.setToolTipText(SHOW_FILENAME_TOOLTIP);
panel.add(showFilenameLabel);
showFilenameCheckbox = new GCheckBox();
showFilenameCheckbox.setToolTipText(SHOW_FILENAME_TOOLTIP);
panel.add(showFilenameCheckbox);
GDLabel useHexLabel = new GDLabel(USE_HEX_LABEL, SwingConstants.RIGHT);
useHexLabel.setToolTipText(USE_HEX_TOOLTIP);
panel.add(useHexLabel);
useHexCheckbox = new GCheckBox();
useHexCheckbox.setToolTipText(USE_HEX_TOOLTIP);
panel.add(useHexCheckbox);
showFilenameCheckbox.addItemListener(evt -> firePropertyChange());
useHexCheckbox.addItemListener(evt -> firePropertyChange());
return panel;
}
@Override
public void setValue(Object value) {
if (!(value instanceof FileOffsetFieldOptionsWrappedOption)) {
return;
}
option = (FileOffsetFieldOptionsWrappedOption) value;
setLocalValues(option);
firePropertyChange();
}
private void setLocalValues(FileOffsetFieldOptionsWrappedOption option) {
if (option.showFilename() != showFilenameCheckbox.isSelected()) {
showFilenameCheckbox.setSelected(option.showFilename());
}
if (option.useHex() != useHexCheckbox.isSelected()) {
useHexCheckbox.setSelected(option.useHex());
}
}
private FileOffsetFieldOptionsWrappedOption cloneFileOffsetValues() {
FileOffsetFieldOptionsWrappedOption newOption = new FileOffsetFieldOptionsWrappedOption();
newOption.setShowFilename(showFilenameCheckbox.isSelected());
newOption.setUseHex(useHexCheckbox.isSelected());
return newOption;
}
@Override
public String[] getOptionDescriptions() {
return DESCRIPTIONS;
}
@Override
public String[] getOptionNames() {
return NAMES;
}
@Override
public Object getValue() {
return cloneFileOffsetValues();
}
@Override
public Component getCustomEditor() {
return editorComponent;
}
@Override
public boolean supportsCustomEditor() {
return true;
}
}

View File

@ -1,115 +0,0 @@
/* ###
* 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.util.viewer.field;
import ghidra.framework.options.CustomOption;
import ghidra.framework.options.GProperties;
/**
* An option class that allows the user to edit a related group of options pertaining to
* File Offset field display
*/
public class FileOffsetFieldOptionsWrappedOption implements CustomOption {
private static final String SHOW_FILENAME = "ShowFilename";
private static final String USE_HEX = "UseHex";
private static final boolean DEFAULT_SHOW_FILENAME = false;
private static final boolean DEFAULT_USE_HEX = true;
private boolean showFilename = DEFAULT_SHOW_FILENAME;
private boolean useHex = DEFAULT_USE_HEX;
/**
* Default constructor, required for persistence
*/
public FileOffsetFieldOptionsWrappedOption() {
}
/**
* Returns whether or not to show the filename
*
* @return True if the filename is to be shown; otherwise, false
*/
public boolean showFilename() {
return showFilename;
}
/**
* Sets whether or not to show the filename
*
* @param showFilename True to show the filename, false to hide it
*/
public void setShowFilename(boolean showFilename) {
this.showFilename = showFilename;
}
/**
* Returns whether or not to display the file offset in hexadecimal
*
* @return True if the file offset is to be displayed in hexadecimal; otherwise, false
*/
public boolean useHex() {
return useHex;
}
/**
* Sets whether or not to display the file offset in hexadecimal
*
* @param useHex True to display the file offset in hexadecimal, false for decimal
*/
public void setUseHex(boolean useHex) {
this.useHex = useHex;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FileOffsetFieldOptionsWrappedOption)) {
return false;
}
if (this == obj) {
return true;
}
FileOffsetFieldOptionsWrappedOption otherOption = (FileOffsetFieldOptionsWrappedOption) obj;
return showFilename == otherOption.showFilename && useHex == otherOption.useHex;
}
@Override
public int hashCode() {
int prime = 31;
int result = 1;
result = prime * result + (showFilename ? 1 : 0);
result = prime * result + (useHex ? 1 : 0);
return result;
}
//==================================================================================================
//Persistence
//==================================================================================================
@Override
public void readState(GProperties properties) {
showFilename = properties.getBoolean(SHOW_FILENAME, showFilename);
useHex = properties.getBoolean(USE_HEX, useHex);
}
@Override
public void writeState(GProperties properties) {
properties.putBoolean(SHOW_FILENAME, showFilename);
properties.putBoolean(USE_HEX, useHex);
}
}

View File

@ -0,0 +1,83 @@
/* ###
* 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.util.viewer.field;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Function;
import ghidra.program.util.OffsetFieldType;
/**
* Generates Function Offset fields
*/
public class FunctionOffsetFieldFactory extends AbstractOffsetFieldFactory {
private static final String FIELD_OFFSET_DESCRIPTION = "Function";
private static final String FIELD_NAME_DESCRIPTION = "Function";
/**
* Creates a new default {@link FunctionOffsetFieldFactory}
*/
public FunctionOffsetFieldFactory() {
super(FIELD_OFFSET_DESCRIPTION);
}
/**
* Creates a new {@link FunctionOffsetFieldFactory}
*
* @param model the {@link FieldFormatModel} that the field belongs to
* @param hlProvider the {@link ListingHighlightProvider}
* @param displayOptions the {@link Options} for display properties
* @param fieldOptions the {@link Options} for field specific properties
*/
private FunctionOffsetFieldFactory(FieldFormatModel model, ListingHighlightProvider hlProvider,
Options displayOptions, Options fieldOptions) {
super(FIELD_OFFSET_DESCRIPTION, FIELD_NAME_DESCRIPTION, model, hlProvider, displayOptions,
fieldOptions);
}
@Override
public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider highlightProvider, ToolOptions options,
ToolOptions fieldOptions) {
return new FunctionOffsetFieldFactory(formatModel, highlightProvider, options,
fieldOptions);
}
@Override
public String getOffsetValue(CodeUnit cu) {
Address addr = cu.getAddress();
Function function = cu.getProgram().getListing().getFunctionContaining(addr);
String text = "";
if (function != null) {
long offset = addr.subtract(function.getEntryPoint());
text = String.format(useHex ? "0x%x" : "%d", offset);
if (showName) {
text = "%s:%s".formatted(function.getName(), text);
}
}
return text;
}
@Override
public OffsetFieldType getOffsetFieldType() {
return OffsetFieldType.FUNCTION;
}
}

View File

@ -0,0 +1,82 @@
/* ###
* 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.util.viewer.field;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.util.OffsetFieldType;
/**
* Generates Imagebase Offset fields
*/
public class ImagebaseOffsetFieldFactory extends AbstractOffsetFieldFactory {
private static final String FIELD_OFFSET_DESCRIPTION = "Imagebase";
private static final String FIELD_NAME_DESCRIPTION = "Field";
/**
* Creates a new default {@link ImagebaseOffsetFieldFactory}
*/
public ImagebaseOffsetFieldFactory() {
super(FIELD_OFFSET_DESCRIPTION);
}
/**
* Creates a new {@link ImagebaseOffsetFieldFactory}
*
* @param model the {@link FieldFormatModel} that the field belongs to
* @param hlProvider the {@link ListingHighlightProvider}
* @param displayOptions the {@link Options} for display properties
* @param fieldOptions the {@link Options} for field specific properties
*/
private ImagebaseOffsetFieldFactory(FieldFormatModel model, ListingHighlightProvider hlProvider,
Options displayOptions, Options fieldOptions) {
super(FIELD_OFFSET_DESCRIPTION, FIELD_NAME_DESCRIPTION, model, hlProvider, displayOptions,
fieldOptions);
}
@Override
public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider highlightProvider, ToolOptions options,
ToolOptions fieldOptions) {
return new ImagebaseOffsetFieldFactory(formatModel, highlightProvider, options,
fieldOptions);
}
@Override
public String getOffsetValue(CodeUnit cu) {
Address addr = cu.getAddress();
Address imagebase = cu.getProgram().getImageBase();
String text = "";
if (addr.hasSameAddressSpace(imagebase)) {
long imagebaseOffset = addr.subtract(cu.getProgram().getImageBase());
text = String.format(useHex ? "0x%x" : "%d", imagebaseOffset);
if (showName) {
text = "imagebase:%s".formatted(text);
}
}
return text;
}
@Override
public OffsetFieldType getOffsetFieldType() {
return OffsetFieldType.IMAGEBASE;
}
}

View File

@ -0,0 +1,83 @@
/* ###
* 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.util.viewer.field;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.OffsetFieldType;
/**
* Generates {@link MemoryBlock} Offset fields
*/
public class MemoryBlockOffsetFieldFactory extends AbstractOffsetFieldFactory {
private static final String FIELD_OFFSET_DESCRIPTION = "MemoryBlock";
private static final String FIELD_NAME_DESCRIPTION = "Memory Block";
/**
* Creates a new default {@link MemoryBlockOffsetFieldFactory}
*/
public MemoryBlockOffsetFieldFactory() {
super(FIELD_OFFSET_DESCRIPTION);
}
/**
* Creates a new {@link MemoryBlockOffsetFieldFactory}
*
* @param model the {@link FieldFormatModel} that the field belongs to
* @param hlProvider the {@link ListingHighlightProvider}
* @param displayOptions the {@link Options} for display properties
* @param fieldOptions the {@link Options} for field specific properties
*/
private MemoryBlockOffsetFieldFactory(FieldFormatModel model,
ListingHighlightProvider hlProvider, Options displayOptions, Options fieldOptions) {
super(FIELD_OFFSET_DESCRIPTION, FIELD_NAME_DESCRIPTION, model, hlProvider, displayOptions,
fieldOptions);
}
@Override
public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider highlightProvider, ToolOptions options,
ToolOptions fieldOptions) {
return new MemoryBlockOffsetFieldFactory(formatModel, highlightProvider, options,
fieldOptions);
}
@Override
public String getOffsetValue(CodeUnit cu) {
Address addr = cu.getAddress();
String text = "";
MemoryBlock block = cu.getProgram().getMemory().getBlock(addr);
if (block != null) {
long offset = addr.subtract(block.getStart());
text = String.format(useHex ? "0x%x" : "%d", offset);
if (showName) {
text = "%s:%s".formatted(block.getName(), text);
}
}
return text;
}
@Override
public OffsetFieldType getOffsetFieldType() {
return OffsetFieldType.MEMORYBLOCK;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -130,6 +130,9 @@ public class ToolOptions extends AbstractOptions {
WrappedOption wo = (WrappedOption) constructor.newInstance();
wo.readState(new SaveState(element));
if (wo instanceof WrappedCustomOption wrappedCustom && !wrappedCustom.isValid()) {
continue;
}
if (wo instanceof WrappedKeyStroke wrappedKs) {
wo = wrappedKs.toWrappedActionTrigger();
}

View File

@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.
@ -21,28 +20,40 @@ import ghidra.util.Msg;
public class WrappedCustomOption implements WrappedOption {
private CustomOption value;
private boolean valid;
public WrappedCustomOption(CustomOption value) {
this();
this.value = value;
}
public WrappedCustomOption() {
this.valid = true;
}
@Override
public void readState(SaveState saveState) {
String customOptionClassName = saveState.getString("CUSTOM OPTION CLASS", null);
valid = false;
try {
Class<?> c = Class.forName(customOptionClassName);
value = (CustomOption) c.newInstance();
value = (CustomOption) c.getConstructor().newInstance();
value.readState(saveState);
valid = true;
}
catch (ClassNotFoundException e) {
Msg.info(this,
"Custom option class '%s' does not exist".formatted(customOptionClassName));
}
catch (Exception e) {
Msg.error(this, "Can't create customOption instance for: " + customOptionClassName, e);
}
}
public boolean isValid() {
return valid;
}
@Override
public void writeState(SaveState saveState) {
saveState.putString("CUSTOM OPTION CLASS", value.getClass().getName());

View File

@ -4,9 +4,9 @@
* 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.
@ -19,28 +19,41 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
/**
* Provides specific information about a program location within the File Offset field
* Provides specific information about a program location within an offset field
*/
public class FileOffsetFieldLocation extends CodeUnitLocation {
public class OffsetFieldLocation extends CodeUnitLocation {
private OffsetFieldType type;
/**
* Creates a new {@link FileOffsetFieldLocation} for the given address
* Creates a new {@link OffsetFieldLocation} for the given address
*
* @param program the program
* @param addr the address of the byte for this location
* @param componentPath the path to data, or null
* @param charOffset the position into the string representation indicating the exact
* position within the field
* @param type The {@link OffsetFieldType type} of offset field
*/
public FileOffsetFieldLocation(Program program, Address addr, int[] componentPath,
int charOffset) {
public OffsetFieldLocation(Program program, Address addr, int[] componentPath, int charOffset,
OffsetFieldType type) {
super(program, addr, componentPath, 0, 0, charOffset);
this.type = type;
}
/**
* Default constructor needed for restoring the field location from XML
*
* @param type The {@link OffsetFieldType type} of offset field
*/
public FileOffsetFieldLocation() {
public OffsetFieldLocation(OffsetFieldType type) {
this.type = type;
}
/**
* {@return the type of offset field}
*/
public OffsetFieldType getType() {
return type;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* 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.program.util;
/**
* The type of offset field
*
* @see OffsetFieldLocation
*/
public enum OffsetFieldType {
FILE,
FUNCTION,
IMAGEBASE,
MEMORYBLOCK
}