mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-1756: New File Offset field and Go To File Offset feature
This commit is contained in:
parent
855853935b
commit
eb324a2b20
10 changed files with 647 additions and 39 deletions
|
@ -557,6 +557,24 @@
|
|||
only the portion of the string that is used will be displayed.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="File_Offset_Field"></A>File Offset Field</H3>
|
||||
|
||||
<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
|
||||
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
|
||||
decimal.</P>
|
||||
<P><IMG src="../../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
|
||||
</A> section.
|
||||
</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Format_Code"></A>Format Code</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
|
|
@ -39,14 +39,15 @@
|
|||
|
||||
<LI>The <I>Go To</I> dialog will be displayed, as shown below:</LI>
|
||||
|
||||
<LI>Enter either an <A href="#gotoaddress">address</A>, <A href="#gotolabel">label</A>, or
|
||||
<A href="#gotoexpression">expression</A> as specified below and press "OK"</LI>
|
||||
<LI>Enter either an <A href="#GoTo_Address">address</A>, <A href="#GoTo_Label">label</A>,
|
||||
<A href="#GoTo_Expression">expression</A>, or <A href="#GoTo_File_Offset">file offset</A>
|
||||
as specified below and press "OK"</LI>
|
||||
|
||||
<LI>If the address, lable, or expression is valid, the Code Browser will be repositioned to
|
||||
that location and the dialog will be dismissed</LI>
|
||||
<LI>If the address, label, expression, or file offset is valid, the Code Browser will be
|
||||
repositioned to that location and the dialog will be dismissed</LI>
|
||||
|
||||
<LI>If the address, label, or expression is not valid, the dialog will display an error
|
||||
message</LI>
|
||||
<LI>If the address, label, expression, or file offset is not valid, the dialog will display
|
||||
an error message</LI>
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
@ -56,7 +57,7 @@
|
|||
<TD width="100%">
|
||||
<P align="center"><IMG alt="" src="images/GoToDialog.png"></P>
|
||||
|
||||
<P align="center"><I><FONT size="3">Go To Address, Label, or Expression
|
||||
<P align="center"><I><FONT size="3">Go To Address, Label, Expression, or File Offset
|
||||
Dialag</FONT></I></P>
|
||||
</TD>
|
||||
</TR>
|
||||
|
@ -67,7 +68,7 @@
|
|||
<BR>
|
||||
|
||||
|
||||
<H3><A name="gotoaddress"></A>Go To Address</H3>
|
||||
<H3><A name="GoTo_Address"></A>Go To Address</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Enter an address into the text area of the dialog. The value entered will be assumed to
|
||||
|
@ -181,7 +182,7 @@
|
|||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<H3><A name="gotolabel"></A>Go To Label</H3>
|
||||
<H3><A name="GoTo_Label"></A>Go To Label</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Enter the name of an existing label into the text area of the dialog. </P>
|
||||
|
@ -216,14 +217,32 @@
|
|||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="gotoexpression"></A>Go To Expression<BR>
|
||||
<H3><A name="GoTo_File_Offset"></A>Go To File Offset<BR>
|
||||
</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Enter <B>file:</B> followed by a file offset of the program's source file bytes (at
|
||||
time of import) into the text area of the dialog. The file offset entered will be assumed
|
||||
to be in decimal, unless it is preceeded by <B>0x</B>. That is, "file:0x1000" and
|
||||
"file:1000" are different values.</P>
|
||||
|
||||
<P><I><IMG src="../../shared/note.png" border="0">Ghidra does not support storing source
|
||||
file bytes for all file formats. Searching for a file offset in these programs will always
|
||||
yield no results.</I></P>
|
||||
|
||||
<P><I><IMG src="../../shared/note.png" border="0">When the program has multiple file byte
|
||||
sources and the destination address is ambiguous, a query results dialog will be displayed.
|
||||
</I></P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="GoTo_Expression"></A>Go To Expression<BR>
|
||||
</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Enter an arithmetic expression that can include addresses, symbols, or can be relative
|
||||
to the current location. All numbers are assumed to be hexadecimal. Supported operator are
|
||||
"+ - * / << >>". Also, parentheses are supported to control order of
|
||||
expresion evaluation.<BR>
|
||||
expression evaluation.<BR>
|
||||
For example:<BR>
|
||||
</P>
|
||||
|
||||
|
@ -247,7 +266,7 @@
|
|||
<TR>
|
||||
<TD><B>0x100000+(2*10)</B>
|
||||
</TD>
|
||||
<TD>Posiitons the cursor at address 0x100020
|
||||
<TD>Positions the cursor at address 0x100020
|
||||
</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.4 KiB |
|
@ -16,8 +16,6 @@
|
|||
package ghidra.app.util.navigation;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -54,7 +52,8 @@ public class GoToAddressLabelDialog extends DialogComponentProvider implements G
|
|||
|
||||
private static final String DIALOG_TITLE = "Go To ...";
|
||||
|
||||
private static final String ANCHOR_NAME = "EXPRESSION";
|
||||
private static final String EXPRESSION_ANCHOR_NAME = "GoTo_Expression";
|
||||
private static final String FILE_OFFSET_ANCHOR_NAME = "GoTo_File_Offset";
|
||||
private static final int DEFAULT_MAX_GOTO_ENTRIES = 10;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -181,31 +180,26 @@ public class GoToAddressLabelDialog extends DialogComponentProvider implements G
|
|||
gbc.gridwidth = 2;
|
||||
gbc.insets = new Insets(5, 5, 5, 5);
|
||||
|
||||
hyperlink = new HyperlinkComponent("<html>Enter an address, label or " + "<a href=\"" +
|
||||
ANCHOR_NAME + "\">expression</a>: ");
|
||||
DockingWindowManager.setHelpLocation(hyperlink,
|
||||
new HelpLocation(HelpTopics.NAVIGATION, "gotoexpression"));
|
||||
hyperlink = new HyperlinkComponent("<html>Enter an address, label, <a href=\"" +
|
||||
EXPRESSION_ANCHOR_NAME + "\">expression</a>, or " +
|
||||
"<a href=\"" + FILE_OFFSET_ANCHOR_NAME +
|
||||
"\">file offset</a>:");
|
||||
|
||||
hyperlink.addHyperlinkListener(ANCHOR_NAME, new HyperlinkListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
|
||||
return;
|
||||
}
|
||||
showExpressionHelp();
|
||||
HyperlinkListener hyperlinkListener = evt -> {
|
||||
if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
||||
HelpLocation loc = new HelpLocation(HelpTopics.NAVIGATION, evt.getDescription());
|
||||
DockingWindowManager.getHelpService().showHelp(loc);
|
||||
}
|
||||
});
|
||||
inner.add(hyperlink);
|
||||
};
|
||||
hyperlink.addHyperlinkListener(EXPRESSION_ANCHOR_NAME, hyperlinkListener);
|
||||
hyperlink.addHyperlinkListener(FILE_OFFSET_ANCHOR_NAME, hyperlinkListener);
|
||||
|
||||
inner.add(hyperlink, gbc);
|
||||
|
||||
comboBox = new GhidraComboBox<>();
|
||||
comboBox.setEditable(true);
|
||||
comboBox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
okCallback();
|
||||
}
|
||||
});
|
||||
comboBox.addActionListener(evt -> okCallback());
|
||||
|
||||
gbc.insets = new Insets(2, 5, 2, 0);
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 1;
|
||||
|
@ -230,11 +224,6 @@ public class GoToAddressLabelDialog extends DialogComponentProvider implements G
|
|||
return mainPanel;
|
||||
}
|
||||
|
||||
protected void showExpressionHelp() {
|
||||
DockingWindowManager.getHelpService().showHelp(hyperlink, false, hyperlink);
|
||||
|
||||
}
|
||||
|
||||
private void writeHistory(SaveState saveState) {
|
||||
String[] strs = new String[history.size()];
|
||||
strs = history.toArray(strs);
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.*;
|
|||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||
import ghidra.GhidraOptions;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
|
@ -48,6 +50,8 @@ import ghidra.util.table.GhidraProgramTableModel;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GoToQuery {
|
||||
private final String FILE_OFFSET_PREFIX = "file:";
|
||||
|
||||
private QueryData queryData;
|
||||
private Address fromAddress;
|
||||
private GhidraProgramTableModel<?> model;
|
||||
|
@ -101,6 +105,9 @@ public class GoToQuery {
|
|||
if (processWildCard()) {
|
||||
return true;
|
||||
}
|
||||
if (processFileOffset()) {
|
||||
return true;
|
||||
}
|
||||
if (processSymbolInParsedScope()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -346,6 +353,27 @@ public class GoToQuery {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean processFileOffset() {
|
||||
String input = queryData.getQueryString();
|
||||
if (StringUtils.startsWithIgnoreCase(input, FILE_OFFSET_PREFIX)) {
|
||||
try {
|
||||
long offset = Long.decode(input.substring(FILE_OFFSET_PREFIX.length()));
|
||||
// NOTE: Addresses are parsed via AbstractAddressSpace.parseString(String addr)
|
||||
Program currentProgram = programs.iterator().next();
|
||||
Memory mem = currentProgram.getMemory();
|
||||
List<Address> addresses = mem.locateAddressesForFileOffset(offset);
|
||||
if (addresses.size() > 0) {
|
||||
goToAddresses(currentProgram, addresses.toArray(new Address[0]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
// fall through to return false
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isWildCard() {
|
||||
String queryInput = queryData.getQueryString();
|
||||
return queryInput.indexOf(PluginConstants.ANYSUBSTRING_WILDCARD_CHAR) > -1 ||
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/* ###
|
||||
* 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.beans.PropertyEditor;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.app.util.HighlightProvider;
|
||||
import ghidra.app.util.viewer.format.FieldFormatModel;
|
||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
||||
import ghidra.framework.options.*;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Generates File Offset fields
|
||||
*/
|
||||
public class FileOffsetFieldFactory extends FieldFactory {
|
||||
|
||||
public static final String FIELD_NAME = "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 PropertyEditor fileOffsetFieldOptionsEditor =
|
||||
new FileOffsetFieldOptionsPropertyEditor();
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*/
|
||||
public FileOffsetFieldFactory() {
|
||||
super(FIELD_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private FileOffsetFieldFactory(FieldFormatModel model, HighlightProvider 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", fileOffsetFieldOptionsEditor);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldFactory newInstance(FieldFormatModel formatModel,
|
||||
HighlightProvider highlightProvider, ToolOptions options, ToolOptions fieldOptions) {
|
||||
return new FileOffsetFieldFactory(formatModel, highlightProvider, options, fieldOptions);
|
||||
}
|
||||
|
||||
@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;
|
||||
Address addr = cu.getAddress();
|
||||
MemoryBlock block = cu.getProgram().getMemory().getBlock(addr);
|
||||
String text = "N/A";
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/* ###
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/* ###
|
||||
* 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.SaveState;
|
||||
|
||||
/**
|
||||
* 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(SaveState saveState) {
|
||||
showFilename = saveState.getBoolean(SHOW_FILENAME, showFilename);
|
||||
useHex = saveState.getBoolean(USE_HEX, useHex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeState(SaveState saveState) {
|
||||
saveState.putBoolean(SHOW_FILENAME, showFilename);
|
||||
saveState.putBoolean(USE_HEX, useHex);
|
||||
}
|
||||
}
|
|
@ -44,13 +44,16 @@ import ghidra.app.plugin.core.table.TableComponentProvider;
|
|||
import ghidra.app.plugin.core.table.TableServicePlugin;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.app.util.MemoryBlockUtils;
|
||||
import ghidra.app.util.PluginConstants;
|
||||
import ghidra.app.util.bin.ByteArrayProvider;
|
||||
import ghidra.app.util.navigation.GoToAddressLabelDialog;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.mem.FileBytes;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.data.*;
|
||||
|
@ -219,6 +222,65 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
|||
assertEquals(addr("0x100494d"), cbPlugin.getCurrentAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoToFileOffset() throws Exception {
|
||||
loadProgram("x86");
|
||||
Memory mem = program.getMemory();
|
||||
|
||||
//@formatter:off
|
||||
// Create a 4-byte and a 1-byte memory block using a 4-byte source FileBytes (just an array
|
||||
// in this case). The 2nd block's byte should share a file byte with the first block so we
|
||||
// can get multiple results when doing a Go To on that file offset.
|
||||
byte[] bytes =
|
||||
/* Block1 |---|---|---|---| */
|
||||
/* FileBytes*/ { 1, 2, 3, 4 } ;
|
||||
/* Block2 |---| */
|
||||
//@formatter:on
|
||||
|
||||
// Create FileBytes-based memory blocks
|
||||
Address addr1 = addr("0x2000");
|
||||
Address addr2 = addr("0x3000");
|
||||
tx(program, () -> {
|
||||
FileBytes fileBytes1 = MemoryBlockUtils.createFileBytes(program,
|
||||
new ByteArrayProvider(program.getName() + "1", bytes), TaskMonitor.DUMMY);
|
||||
FileBytes fileBytes2 = MemoryBlockUtils.createFileBytes(program,
|
||||
new ByteArrayProvider(program.getName() + "2", bytes), TaskMonitor.DUMMY);
|
||||
mem.createInitializedBlock("FileBytes1", addr1, fileBytes1, 0, 4, false);
|
||||
mem.createInitializedBlock("FileBytes2", addr2, fileBytes2, 3, 1, false);
|
||||
});
|
||||
|
||||
// Test decimal
|
||||
setText("file:0");
|
||||
showDialog();
|
||||
performOkCallback();
|
||||
assertEquals(addr1, cbPlugin.getCurrentAddress());
|
||||
|
||||
// Test hex
|
||||
setText("file:0x1");
|
||||
showDialog();
|
||||
performOkCallback();
|
||||
assertEquals(addr1.add(1), cbPlugin.getCurrentAddress());
|
||||
|
||||
// Test "case"
|
||||
setText("FILE:2");
|
||||
showDialog();
|
||||
performOkCallback();
|
||||
assertEquals(addr1.add(2), cbPlugin.getCurrentAddress());
|
||||
|
||||
// Test not found
|
||||
setText("file:0x100");
|
||||
showDialog();
|
||||
performOkCallback();
|
||||
assertNotEquals(addr1.add(0x100), cbPlugin.getCurrentAddress());
|
||||
|
||||
// Test multiple results
|
||||
setText("file:3");
|
||||
showDialog();
|
||||
performOkCallback();
|
||||
GhidraProgramTableModel<?> model = waitForModel();
|
||||
assertEquals(2, model.getRowCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScopedSymbol() throws Exception {
|
||||
loadProgram("x86");
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Provides specific information about a program location within the File Offset field
|
||||
*/
|
||||
public class FileOffsetFieldLocation extends CodeUnitLocation {
|
||||
|
||||
/**
|
||||
* Creates a new {@link FileOffsetFieldLocation} 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
|
||||
*/
|
||||
public FileOffsetFieldLocation(Program program, Address addr, int[] componentPath,
|
||||
int charOffset) {
|
||||
super(program, addr, componentPath, 0, 0, charOffset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor needed for restoring the field location from XML
|
||||
*/
|
||||
public FileOffsetFieldLocation() {
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue