diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm index 5f988a63af..23ee311d59 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm @@ -556,6 +556,24 @@ be made as small as possible. For example, when showing an offcut string reference, only the portion of the string that is used will be displayed.
+ ++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.
+Show Filename - 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.
+Show Numbers In Hex - Option to display the file offset in hexadecimal rather than + decimal.
++ +
The File Offset field is disabled by default. + To enable the field, see the Enable Field + section. +
Go To Address, Label, or Expression
+ Go To Address, Label, Expression, or File Offset
Dialag
+ +Enter an address into the text area of the dialog. The value entered will be assumed to @@ -181,7 +182,7 @@ -
Go To Label
+Go To Label
Enter the name of an existing label into the text area of the dialog.
@@ -215,15 +216,33 @@
++ +Enter file: 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 0x. That is, "file:0x1000" and + "file:1000" are different values.
-Go To Expression
++ +
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.
+
When the program has multiple file byte + sources and the destination address is ambiguous, a query results dialog will be displayed. +
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.
@@ -247,7 +266,7 @@
+ expression evaluation.
For example:
0x100000+(2*10) -Posiitons the cursor at address 0x100020 + Positions the cursor at address 0x100020 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/images/GoToDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/images/GoToDialog.png index e6f5f9ba88..cd9f92285e 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/images/GoToDialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/images/GoToDialog.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToAddressLabelDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToAddressLabelDialog.java index 0dd6e957f5..b719261fb7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToAddressLabelDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToAddressLabelDialog.java @@ -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("Enter an address, label or " + "expression: "); - DockingWindowManager.setHelpLocation(hyperlink, - new HelpLocation(HelpTopics.NAVIGATION, "gotoexpression")); + hyperlink = new HyperlinkComponent("Enter an address, label, expression, or " + + "file offset:"); - 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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java index 74ecf9a085..608ad8d0e4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java @@ -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 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 || diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldFactory.java new file mode 100644 index 0000000000..2a4c0f1336 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldFactory.java @@ -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); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsPropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsPropertyEditor.java new file mode 100644 index 0000000000..218edfa863 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsPropertyEditor.java @@ -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; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsWrappedOption.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsWrappedOption.java new file mode 100644 index 0000000000..a07580e5d9 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/FileOffsetFieldOptionsWrappedOption.java @@ -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); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java index 8a76e98420..4760469f09 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java @@ -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"); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FileOffsetFieldLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FileOffsetFieldLocation.java new file mode 100644 index 0000000000..d19384715b --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FileOffsetFieldLocation.java @@ -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() { + } +}