From a5b643f3922f1d5362c0afc94ed5bd19bb739bb1 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 25 Jan 2022 11:21:51 -0500 Subject: [PATCH] GP-1684: Add 'Patch Data' action and fix encoders. --- Ghidra/Features/Base/certification.manifest | 1 - .../help/topics/AssemblerPlugin/Assembler.htm | 158 ++++--- .../AssemblerPlugin/images/Assembler.png | Bin 66780 -> 77981 bytes .../core/assembler/AbstractPatchAction.java | 384 +++++++++++++++++ .../core/assembler/AssembleDockingAction.java | 403 ------------------ .../core/assembler/AssemblerPlugin.java | 44 +- .../core/assembler/AssemblyDualTextField.java | 64 ++- .../core/assembler/PatchDataAction.java | 153 +++++++ .../assembler/PatchInstructionAction.java | 290 +++++++++++++ .../core/assembler/AssemblerPluginTest.java | 258 +++++++++++ .../autocomplete/TextFieldAutocompleter.java | 158 ++++--- .../widgets/textfield/TextFieldLinker.java | 9 + .../settings/FormatSettingsDefinition.java | 17 +- .../model/data/AbstractIntegerDataType.java | 78 +++- .../model/data/DataTypeEncodeException.java | 25 +- .../model/data/IntegerDataTypeTest.java | 224 ++++++++++ .../assembler/AssemblerPluginScreenShots.java | 44 ++ .../AssemblerPluginScreenShots.java | 66 --- 18 files changed, 1721 insertions(+), 655 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AbstractPatchAction.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/assembler/AssemblerPluginTest.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/IntegerDataTypeTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/screen/java/ghidra/app/plugin/core/assembler/AssemblerPluginScreenShots.java delete mode 100644 Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AssemblerPluginScreenShots.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 916c438c64..ec24544237 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -899,7 +899,6 @@ src/main/resources/help/Dummy_HelpSet.hs||GHIDRA||reviewed||END| src/main/resources/help/empty.htm||GHIDRA||||END| src/main/resources/images/2leftarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/2rightarrow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| -src/main/resources/images/ArmedMarkSelection.png||GHIDRA||||END| src/main/resources/images/Array.png||GHIDRA||||END| src/main/resources/images/B.gif||GHIDRA||||END| src/main/resources/images/BookShelf.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/Assembler.htm b/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/Assembler.htm index 48fd5e2aff..17f0f8e132 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/Assembler.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/Assembler.htm @@ -9,109 +9,99 @@ Assembler - - -

Assembler

+

Assembler

-

The Patch Instruction action will allow you to edit - the current assembly instruction in the listing. The first time you use the action, it may take - a moment to prepare the assembler for your processor. You can then edit the text of the - instruction, optionally replacing it altogether. As you edit, a content assists will provide - completion suggestions. It will present a list of byte sequences when the text comprises a - complete instruction. Activating a completion suggestion will input that text at your cursor. - Activating a byte sequence will complete the action, replacing the instruction at your cursor. - Pressing ESC or clicking outside of the assembly editor will cancel the action.

- -

+

Assembly Editor

-
-

To edit assembly, select Patch Instruction from the - Listing View context menu or - press CTRL-SHIFT-G on the instruction to modify. Click the plus button below the content - assist to exhaust the undefined bits.

-
+

The Assembler plugin provides actions for modifying program bytes by inputing mnemonic + assembly and data values.

+ +

Actions

+ +

There are two actions: One for instructions and one for data.

+ +

Patch Instruction

+ +

This action is available at any initialized memory address. It allows you to edit the + current assembly instruction in the listing. The first time you use the action, it may take a + moment to prepare the assembler for the current program's processor language. You can then edit + the text of the instruction, optionally replacing it altogether. As you edit, a content assist + will provide completion suggestions. When the text comprises a valid instruction, it will also + display assembled byte sequences. Activating a suggestion will input that text at your cursor. + Activating a byte sequence will finish the action, replacing the instruction at the current + address. Pressing ESC or clicking outside of the assembly editor will cancel the action.

+ +

At times, the assembler will generate undefined bits. Typically, these bits are actually + reserved, and so by default, the assembler fills them with 0s. The toggle below the content + assist will cause the assembler to fill them will all possible patterns of 1s and 0s. This + toggle should be used with care, since some languages may generate many undefined bits, and + each bit grows the list by a factor of 2.

Ghidra's assembler is based on the same SLEIGH modeling that powers the disassembler. This offers some nice benefits:

- + - -

Keep in mind, the above list is in an ideal world. We are in the process of improving the - assembly engine and processor modules to eventually support assembly for all of Ghidra's - processors. In the meantime, we test several popular processors and assign a performance rating - to each. The possible ratings are:

- + +

Keep in mind, the above list is in an ideal world. The assembler essentially "solves" the + disassembly routine for its input given a desired output. While the process works well in + general, some languages require special attention. Thus, we periodically test our processor + languages for assembly support and assign each a performance rating. The possible ratings + are:

+
  1. Platinum: Our automated tests did not find any errors. This offers the best possible user experience.
  2. -
  3. Gold: You will rarely encounter an error. You will find it very useful.
  4. -
  5. Silver: You may occasionally encounter an error, but the assembler is still - usable. You will likely find it useful with occasional frustration.
  6. -
  7. Bronze: You are likely to encounter errors, but there are enough working - instructions that the assembler is useful. You may find it useful, but it will probably be - frustrating.
  8. -
  9. Poor: You are likely to encounter severe errors, and there are few instructions - that assemble. You may or may not find it useful, but we consider it unusable.
  10. -
  11. Unrated: The processor is not tested, or the test failed before a rating could be - assigned. You might get lucky, but don't count on it.
  12. + +
  13. Gold: Our automated tests found a couple of small errors. You should rarely + encounter issues.
  14. + +
  15. Silver: Our automated tests found a handful of errors, or perhaps one large class + of errors. You will likely encounter one, but still find the assembler useful.
  16. + +
  17. Bronze: Our automated tests found many errors, or perhaps a few large classes of + errors. You are very likely to encounter them, but still may find the assembler useful, if + not just a bit frustrating.
  18. + +
  19. Poor: Our automated tests found many severe errors, and/or several large classes + of errors, or the tests could not complete. You will likely encounter an error the first time + you try using it. A few instructions may assemble correctly, but it'll be more frustrating + than useful.
  20. + +
  21. Unrated: The processor is not tested. Your experience will depend on the + complexity of the processor language. In general, the more SLEIGH context operations, + especially those preceding the main instruction table, the more issues that may arise.
- -

As of this release, our tested processors fall under Platinum, Gold, or Poor.

- - + +

If the current processor scored anything less than Platinum you will receive a + friendly warning reminding you what to expect.

+ +

Patch Data

+ +

This action is available at initialized memory addresses having a data unit with a type that + provides an encoder. It allows you to edit the current data unit by typing its string + representation. There is (currently) no content assist, and generally only primitive types and + strings are supported. NOTE: Strings must be enclosed in double quotes, since that is + how they are displayed in the listing. Pressing ENTER attempts to encode the data and place it + at the current address. If this fails, the error is displayed in Ghidra's status bar, and the + input fields remain on screen.

Provided by: Assembler plugin

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/images/Assembler.png b/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/images/Assembler.png index c7720e756e8dafe583573afff105e18aaec6f968..7e05dfe03ad8a5ffc37fff823286642e63c525d6 100644 GIT binary patch literal 77981 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%0A7bxY0>Q<*b1V~1jbQfr5hVSD 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 M5(?tQqR-#_KX6{`{Qv*} literal 66780 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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AbstractPatchAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AbstractPatchAction.java new file mode 100644 index 0000000000..f0bf95f198 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AbstractPatchAction.java @@ -0,0 +1,384 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.assembler; + +import java.awt.*; +import java.awt.event.*; +import java.math.BigInteger; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import docking.widgets.fieldpanel.*; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.GhidraOptions; +import ghidra.app.context.ListingActionContext; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.util.viewer.field.ListingField; +import ghidra.app.util.viewer.listingpanel.ListingModelAdapter; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.util.ProgramLocation; + +/** + * An abstract action for patching + * + *

+ * This handles most of the field placement, but relies on quite a few callbacks. + */ +public abstract class AbstractPatchAction extends DockingAction { + protected static final String MENU_GROUP = "Disassembly"; + + protected final PluginTool tool; + + private FieldPanelOverLayoutManager fieldLayoutManager; + private CodeViewerProvider codeViewerProvider; + private FieldPanel fieldPanel; + private ListingPanel listingPanel; + + private Program program; + private Address address; + + private final KeyListener listenerForKeys = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.isConsumed()) { + return; + } + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + cancel(); + e.consume(); + break; + case KeyEvent.VK_ENTER: + accept(); + e.consume(); + break; + } + } + }; + + private final FocusListener listenerForFocusLost = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + cancel(); + } + }; + + /** + * Create a new action owned by the given plugin, having the given name + * + * @param owner the plugin owning the action + * @param name the name of the action + */ + public AbstractPatchAction(Plugin owner, String name) { + super(name, owner.getName()); + tool = owner.getTool(); + } + + /** + * Initialize the action, post construction + */ + protected void init() { + addInputFocusListener(listenerForFocusLost); + addInputKeyListener(listenerForKeys); + } + + /** + * Add the given focus listener to your input field(s) + * + *

+ * The action uses this to know when those fields have lost focus, so it can cancel the action. + * + * @param listener the listener + */ + protected abstract void addInputFocusListener(FocusListener listener); + + /** + * Add the given key listener to your input field(s) + * + *

+ * The listener handles Escape and Enter, canceling or accepting the input, respectively. + * + * @param listener the listener + */ + protected abstract void addInputKeyListener(KeyListener listener); + + /** + * If needed, add your layout listeners to this action's layout manager + * + *

+ * If there are additional components that need to move, e.g., when the panel is scrolled, then + * you need a layout listener. If this is overridden, then + * {@link #removeLayoutListeners(FieldPanelOverLayoutManager)} must also be overridden. + * + * @param fieldLayoutManager the layout manager + */ + protected void addLayoutListeners(FieldPanelOverLayoutManager fieldLayoutManager) { + // Extension point + } + + /** + * Remove your layout listeners from this action's layout manager + * + * @see #addLayoutListeners(FieldPanelOverLayoutManager) + */ + protected void removeLayoutListeners(FieldPanelOverLayoutManager fieldLayoutManager) { + // Extension point + } + + /** + * Set your input field(s) font to the given one + * + *

+ * This ensures your field's font matches the listing over which it is placed. + * + * @param font the listing's base font + */ + protected abstract void setInputFont(Font font); + + /** + * Get the program on which this action was invoked + * + * @return the current program + */ + protected Program getProgram() { + return program; + } + + /** + * Get the address at which this action was invoked + * + * @return the current address + */ + protected Address getAddress() { + return address; + } + + @Override + public void dispose() { + super.dispose(); + if (fieldLayoutManager != null) { + fieldPanel.setLayout(null); + fieldLayoutManager.unregister(); + removeLayoutListeners(fieldLayoutManager); + } + } + + private void prepareLayout(ListingActionContext context) { + ComponentProvider contextProvider = context.getComponentProvider(); + if (codeViewerProvider == contextProvider) { + return; + } + + if (codeViewerProvider != null) { + fieldPanel.setLayout(null); + fieldLayoutManager.unregister(); + removeLayoutListeners(fieldLayoutManager); + } + + codeViewerProvider = (CodeViewerProvider) contextProvider; + listingPanel = codeViewerProvider.getListingPanel(); + fieldPanel = listingPanel.getFieldPanel(); + + fieldLayoutManager = new FieldPanelOverLayoutManager(fieldPanel); + fieldPanel.setLayout(fieldLayoutManager); + addLayoutListeners(fieldLayoutManager); + } + + /** + * Invoked when the user presses Enter + * + *

+ * This should validate the user's input and complete the action. If the action is completed + * successfully, then call {@link #hide()}. Note that the Enter key can be ignored by doing + * nothing, since the input field(s) will remain visible. In that case, you must provide another + * mechanism for completing the action. + */ + public abstract void accept(); + + /** + * Hide the input field(s) + * + *

+ * This removes any components added to the listing's field panel, usually via + * {@link #showInputs(FieldPanel)}, and returns focus to the listing. If other components were + * added elsewhere, you must override this and hide them, too. + */ + protected void hide() { + fieldPanel.removeAll(); + fieldLayoutManager.layoutContainer(fieldPanel); + fieldPanel.requestFocusInWindow(); + } + + /** + * Cancel the current patch action + * + *

+ * This hides the input field(s) without completing the action. + */ + public void cancel() { + hide(); + } + + /** + * Locate a listing field by name and address + * + *

+ * Generally, this is used in {@link #showInputs(FieldPanel)} to find constraints suitable for + * use in {@link Container#add(Component, Object)} on the passed {@code fieldPanel}. Likely, the + * address should be obtained from {@link #getAddress()}. + * + * @param address the address for the line (row) in the listing + * @param fieldName the column name for the field in the listing + * @return if found, the field location, or null + */ + protected FieldLocation findFieldLocation(Address address, String fieldName) { + Layout layout = listingPanel.getLayout(address); + ListingModelAdapter adapter = (ListingModelAdapter) fieldPanel.getLayoutModel(); + BigInteger index = adapter.getAddressIndexMap().getIndex(address); + int count = layout.getNumFields(); + for (int i = 0; i < count; i++) { + ListingField field = (ListingField) layout.getField(i); + if (field.getFieldFactory().getFieldName().equals(fieldName)) { + return new FieldLocation(index, i); + } + } + return null; + } + + /** + * Check if the action is applicable to the given code unit + * + * @param unit the code unit at the user's cursor + * @return true if applicable, false if not + */ + protected abstract boolean isApplicableToUnit(CodeUnit unit); + + @Override + public boolean isAddToPopup(ActionContext context) { + if (!(context instanceof ListingActionContext)) { + return false; + } + + ListingActionContext lac = (ListingActionContext) context; + if (!isApplicableToUnit(lac.getCodeUnit())) { + return false; + } + + ComponentProvider provider = lac.getComponentProvider(); + if (!(provider instanceof CodeViewerProvider)) { + return false; + } + + CodeViewerProvider codeViewer = (CodeViewerProvider) provider; + if (codeViewer.isReadOnly()) { + return false; + } + + Program program = lac.getProgram(); + if (program == null) { + return false; + } + + Address address = lac.getAddress(); + if (address == null) { + return false; + } + MemoryBlock block = program.getMemory().getBlock(address); + if (block == null || !block.isInitialized()) { + return false; + } + return true; + } + + /** + * Perform preparation and save any information needed later in {@link #accept()}. + * + * @param unit the code unit at the user's cursor + */ + protected abstract void prepare(CodeUnit unit); + + /** + * Put the input fields in their place, show them, and place focus appropriately + * + *

+ * Use {{@link #findFieldLocation(Address, String)} to locate fields in the listing and place + * your inputs over them. + * + * @param address the address of the user's cursor + * @param fieldPanel the currently-focused listing field panel + * @return false if inputs could not be placed and shown + */ + protected abstract boolean showInputs(FieldPanel fieldPanel); + + /** + * Pre-fill the input fields and place the caret appropriately (usually at the end) + * + * @param unit the code unit at the user's cursor + */ + protected abstract void fillInputs(CodeUnit unit); + + @Override + public void actionPerformed(ActionContext context) { + if (!(context instanceof ListingActionContext)) { + return; + } + + ListingActionContext lac = (ListingActionContext) context; + prepareLayout(lac); + if (codeViewerProvider.isReadOnly()) { + return; + } + CodeUnit cu = lac.getCodeUnit(); + if (!isApplicableToUnit(cu)) { + return; + } + + ProgramLocation cur = lac.getLocation(); + program = cur.getProgram(); + address = cur.getAddress(); + if (address == null) { + return; + } + MemoryBlock block = program.getMemory().getBlock(address); + if (block == null || !block.isInitialized()) { + return; + } + + prepare(cu); + + ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); + Font font = displayOptions.getFont(GhidraOptions.OPTION_BASE_FONT, null); + if (font != null) { + setInputFont(font); + } + + fieldPanel.removeAll(); + if (!showInputs(fieldPanel)) { + return; + } + fillInputs(cu); + + fieldLayoutManager.layoutContainer(fieldPanel); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java deleted file mode 100644 index 13821413dd..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssembleDockingAction.java +++ /dev/null @@ -1,403 +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.plugin.core.assembler; - -import java.awt.Color; -import java.awt.Font; -import java.awt.event.*; -import java.math.BigInteger; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.KeyStroke; - -import org.apache.commons.collections4.map.DefaultedMap; -import org.apache.commons.collections4.map.LazyMap; - -import docking.*; -import docking.action.*; -import docking.widgets.autocomplete.*; -import docking.widgets.fieldpanel.*; -import docking.widgets.fieldpanel.support.FieldLocation; -import ghidra.GhidraOptions; -import ghidra.app.context.ListingActionContext; -import ghidra.app.plugin.assembler.Assembler; -import ghidra.app.plugin.assembler.Assemblers; -import ghidra.app.plugin.core.assembler.AssemblyDualTextField.*; -import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.app.util.viewer.field.ListingField; -import ghidra.app.util.viewer.listingpanel.ListingModelAdapter; -import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.framework.options.ToolOptions; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.database.util.ProgramTransaction; -import ghidra.program.model.address.Address; -import ghidra.program.model.lang.Language; -import ghidra.program.model.listing.*; -import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.util.ProgramLocation; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; -import ghidra.util.task.CachingSwingWorker; -import ghidra.util.task.TaskMonitor; - -/** - * A context menu action to assemble an instruction at the current address - */ -public class AssembleDockingAction extends DockingAction { - private static final String ASSEMBLY_RATING = "assemblyRating"; - private static final String ASSEMBLY_MESSAGE = "assemblyMessage"; - private static final KeyStroke KEYBIND_ASSEMBLE = KeyStroke.getKeyStroke(KeyEvent.VK_G, - DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK); - //private PluginTool tool; - private FieldPanelOverLayoutManager fieldLayoutManager; - private CodeViewerProvider cv; - private FieldPanel codepane; - private ListingPanel listpane; - private Map> cache = - LazyMap.lazyMap(new HashMap<>(), language -> new AssemblerConstructorWorker(language)); - - private Map shownWarning = DefaultedMap.defaultedMap(new HashMap<>(), false); - - private final AssemblyDualTextField input = new AssemblyDualTextField(); - private Program prog; - private Address addr; - private Language lang; - private Assembler assembler; - private final MyListener listener = new MyListener(); - private final PluginTool tool; - - // Callback to keep the autocompleter positioned under the fields - private FieldPanelOverLayoutListener autoCompleteMover = (FieldPanelOverLayoutEvent ev) -> { - TextFieldAutocompleter autocompleter = input.getAutocompleter(); - if (autocompleter.isCompletionListVisible()) { - autocompleter.updateDisplayLocation(); - } - }; - - // To build the assembler in the background if it takes a while - private static class AssemblerConstructorWorker extends CachingSwingWorker { - private Language lang; - - public AssemblerConstructorWorker(Language lang) { - super("Assemble", false); - this.lang = lang; - } - - @Override - protected Assembler runInBackground(TaskMonitor monitor) { - monitor.setMessage("Constructing assembler for " + lang); - return Assemblers.getAssembler(lang); - } - } - - /* - * A class for all my callbacks - * - * For autocompletion, this causes activation of an assembled instruction to actually patch the - * instruction in. - * - * For keyboard, it causes the escape key, if not already consumed by the autocompleter, to - * cancel the assembly action altogether. - */ - private class MyListener implements AutocompletionListener, KeyListener { - @Override - public void completionActivated(AutocompletionEvent ev) { - if (ev.getSelection() instanceof AssemblyInstruction) { - AssemblyInstruction ins = (AssemblyInstruction) ev.getSelection(); - try (ProgramTransaction trans = - ProgramTransaction.open(prog, "Assemble @" + addr + ": " + input.getText())) { - assembler.patchProgram(ins.getData(), addr); - trans.commit(); - cancel(); // Not really, since I've committed. Just hides the editors. - return; - } - catch (MemoryAccessException e) { - Msg.showError(assembler, input.getMnemonicField().getRootPane(), "Assemble", - "Could not patch selected instruction", e); - } - } - } - - @Override - public void keyTyped(KeyEvent e) { - // Blank - } - - @Override - public void keyPressed(KeyEvent e) { - if (e.isConsumed()) { - return; - } - if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - cancel(); - e.consume(); - } - } - - @Override - public void keyReleased(KeyEvent e) { - // Blank - } - } - - public AssembleDockingAction(PluginTool tool, String name, String owner) { - super(name, owner); - this.tool = tool; - - String group = "Disassembly"; - setPopupMenuData(new MenuData(new String[] { "Patch Instruction" }, group)); - setKeyBindingData(new KeyBindingData(KEYBIND_ASSEMBLE)); - setHelpLocation(new HelpLocation("AssemblerPlugin", "AssembleAction")); - - // If I lose focus, cancel the assembly - input.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - // Blank - } - - @Override - public void focusLost(FocusEvent e) { - cancel(); - } - }); - - input.getMnemonicField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); - input.getOperandsField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); - input.getAssemblyField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); - - input.getAutocompleter().addAutocompletionListener(listener); - input.addKeyListener(listener); - } - - @Override - public void dispose() { - super.dispose(); - input.dispose(); - } - - protected void prepareLayout(ActionContext context) { - ComponentProvider prov = context.getComponentProvider(); - if (cv == prov) { - return; - } - - if (cv != null) { - codepane.setLayout(null); - fieldLayoutManager.removeLayoutListener(autoCompleteMover); - } - - // we are only added to the popup for a ListingActionContext that has a CodeViewerProvider - cv = (CodeViewerProvider) prov; - listpane = cv.getListingPanel(); - codepane = listpane.getFieldPanel(); - - fieldLayoutManager = new FieldPanelOverLayoutManager(codepane); - codepane.setLayout(fieldLayoutManager); - fieldLayoutManager.addLayoutListener(autoCompleteMover); - } - - /** - * Cancel the current assembly action - */ - public void cancel() { - codepane.removeAll(); - //codepane.repaint(); - fieldLayoutManager.layoutContainer(codepane); - codepane.requestFocusInWindow(); - } - - /** - * Retrieve the location in the code viewer's {@link FieldPanel} for the field at the given - * address having the given header text - * - * @param address the address - * @param fieldName the name of the field - * @return if found, the {@link FieldLocation}, otherwise {@code null} - */ - protected FieldLocation findFieldLocation(Address address, String fieldName) { - Layout layout = listpane.getLayout(address); - ListingModelAdapter adapter = (ListingModelAdapter) codepane.getLayoutModel(); - BigInteger index = adapter.getAddressIndexMap().getIndex(address); - int count = layout.getNumFields(); - for (int i = 0; i < count; i++) { - ListingField field = (ListingField) layout.getField(i); - if (field.getFieldFactory().getFieldName().equals(fieldName)) { - return new FieldLocation(index, i); - } - } - return null; - } - - static enum AssemblyRating { - UNRATED("This processor has not been tested with the assembler." + - " If you are really lucky, the assembler will work on this language." + - " Please contact the Ghidra team if you'd like us to test, rate, and/or improve this language."), - POOR("This processor received a rating of POOR during testing." + - " Please contact the Ghidra team if you'd like to assemble for this language." + - " Until then, we DO NOT recommend trying to assemble."), - BRONZE("This processor received a rating of BRONZE during testing." + - " Please contact the Ghidra team if you'd like to assemble for this language." + - " A fair number of instruction may assemble, but we DO NOT recommend trying to assemble."), - SILVER("This processor received a rating of SILVER during testing." + - " Most instruction should work, but you will likely encounter a few errors." + - " Please contact the Ghidra team if you'd like certain instruction improved."), - GOLD("This processor received a rating of GOLD during testing." + - " You should rarely encounter an error, but please let us know if you do."), - PLATINUM("This processor received a rating of PLATINUM during testing."); - - final String message; - - private AssemblyRating(String message) { - this.message = message; - } - } - - @Override - public void actionPerformed(ActionContext context) { - if (!(context instanceof ListingActionContext)) { - return; - } - prepareLayout(context); - if (cv.isReadOnly()) { - return; - } - ListingActionContext lac = (ListingActionContext) context; - - ProgramLocation cur = lac.getLocation(); - - prog = cur.getProgram(); - addr = cur.getAddress(); - MemoryBlock block = prog.getMemory().getBlock(addr); - if (block == null || !block.isInitialized()) { - return; - } - lang = prog.getLanguage(); - - AssemblyRating rating = - AssemblyRating.valueOf(lang.getProperty(ASSEMBLY_RATING + ":" + lang.getLanguageID(), - AssemblyRating.UNRATED.name())); - if (AssemblyRating.PLATINUM != rating) { - String message = - lang.getProperty(ASSEMBLY_MESSAGE + ":" + lang.getLanguageID(), rating.message); - if (!shownWarning.get(lang)) { - Msg.showWarn(this, cv.getComponent(), "Assembler Rating", - "

" + message + "

"); - shownWarning.put(lang, true); - } - } - - cache.get(lang).get(null); - assembler = Assemblers.getAssembler(prog); - - ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); - Font font = displayOptions.getFont(GhidraOptions.OPTION_BASE_FONT, null); - if (font != null) { - input.setFont(font); - } - - input.setProgramLocation(cur); - FieldLocation locMnem = findFieldLocation(addr, "Mnemonic"); - if (null == locMnem) { - Msg.showError(this, codepane, "Assemble", - "The mnemonic field must be present to assemble"); - return; - } - FieldLocation locOpns = findFieldLocation(addr, "Operands"); - - codepane.removeAll(); - if (null == locOpns) { - // There is no operands field. Use the single-box variant - codepane.add(input.getAssemblyField(), locMnem); - input.setVisible(VisibilityMode.SINGLE_VISIBLE); - } - else { - // Use the split-field variant - codepane.add(input.getMnemonicField(), locMnem); - codepane.add(input.getOperandsField(), locOpns); - input.setVisible(VisibilityMode.DUAL_VISIBLE); - } - - // Set the default text, only if it's currently an instruction - CodeUnit cu = prog.getListing().getCodeUnitAt(addr); - if (cu instanceof Instruction) { - Instruction ins = (Instruction) cu; - String instr = ins.toString(); - if (ins.isInDelaySlot()) { - assert instr.startsWith("_"); - instr = instr.substring(1).trim(); - } - input.setText(instr); - input.setCaretPosition(instr.length()); - if (null == locOpns) { - input.getAssemblyField().grabFocus(); - } - else if (instr.contains(" ")) { - input.getOperandsField().grabFocus(); - } - else { - input.getMnemonicField().grabFocus(); - } - } - else { - input.setText(""); - input.setCaretPosition(0); - if (null == locOpns) { - input.getAssemblyField().grabFocus(); - } - else { - input.getMnemonicField().grabFocus(); - } - } - fieldLayoutManager.layoutContainer(codepane); - //JTextField opns = dual.getOperandsField(); - //opns.grabFocus(); - //opns.setCaretPosition(opns.getText().length()); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - - // currently only works on a listing that has a CodeViewerProvider - if (!(context instanceof ListingActionContext)) { - return false; - } - - ListingActionContext lac = (ListingActionContext) context; - ComponentProvider cp = lac.getComponentProvider(); - if (!(cp instanceof CodeViewerProvider)) { - return false; - } - - CodeViewerProvider codeViewer = (CodeViewerProvider) cp; - if (codeViewer.isReadOnly()) { - return false; - } - - Program program = lac.getProgram(); - if (program == null) { - return false; - } - MemoryBlock block = program.getMemory().getBlock(lac.getAddress()); - if (block == null || !block.isInitialized()) { - return false; - } - return true; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java index 30b8450eee..468514d339 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblerPlugin.java @@ -15,42 +15,46 @@ */ package ghidra.app.plugin.core.assembler; -import docking.action.DockingAction; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.assembler.Assemblers; +import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.data.DataType; +import ghidra.program.model.mem.MemBuffer; /** * A plugin for assembly * - * This plugin currently provides a single action: {@link AssembleDockingAction}, which allows the - * user to assemble an instruction at the current address. + *

+ * This plugin currently provides two actions: {@link PatchInstructionAction}, which allows the user + * to assemble an instruction at the current address; and {@link PatchDataAction}, which allows the + * user to "assemble" data at the current address. * - * The API for assembly is available from {@link Assemblers}. + *

+ * The API for instruction assembly is available from {@link Assemblers}. For data assembly, the API + * is in {@link DataType#encodeRepresentation(String, MemBuffer, Settings, int)}. */ -//@formatter:off @PluginInfo( status = PluginStatus.RELEASED, packageName = CorePluginPackage.NAME, category = PluginCategoryNames.PATCHING, shortDescription = "Assembler", description = "This plugin provides functionality for assembly patching. " + - "The assembler supports most processor languages also supported by the " + - "disassembler. Depending on the particular processor, your mileage may vary. " + - "We are in the process of testing and improving support for all our processors. " + - "You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " + - "instruction in place. As you type, a content assist will guide you and provide " + - "assembled bytes when you have a complete instruction." -) -//@formatter:on + "The assembler supports most processor languages also supported by the " + + "disassembler. Depending on the particular processor, your mileage may vary. " + + "We are in the process of testing and improving support for all our processors. " + + "You can access the assembler by pressing Ctrl-Shift-G, and then modifying the " + + "instruction in place. As you type, a content assist will guide you and provide " + + "assembled bytes when you have a complete instruction.") public class AssemblerPlugin extends ProgramPlugin { public static final String ASSEMBLER_NAME = "Assembler"; - private DockingAction assembleAction; + /*test*/ PatchInstructionAction patchInstructionAction; + /*test*/ PatchDataAction patchDataAction; public AssemblerPlugin(PluginTool tool) { super(tool, false, false, false); @@ -58,14 +62,16 @@ public class AssemblerPlugin extends ProgramPlugin { } private void createActions() { - assembleAction = new AssembleDockingAction(tool, "Assemble", getName()); - assembleAction.setEnabled(true); - tool.addAction(assembleAction); + patchInstructionAction = new PatchInstructionAction(this, "Patch Instruction"); + tool.addAction(patchInstructionAction); + + patchDataAction = new PatchDataAction(this, "Patch Data"); + tool.addAction(patchDataAction); } @Override protected void dispose() { - assembleAction.dispose(); + patchInstructionAction.dispose(); + patchDataAction.dispose(); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java index 239b196d1d..64a4147d2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java @@ -73,7 +73,7 @@ public class AssemblyDualTextField { protected Program program; protected Assembler assembler; - protected Address addr; + protected Address address; protected Instruction existing; protected boolean exhaustUndefined = false; @@ -415,6 +415,13 @@ public class AssemblyDualTextField { auto.dispose(); } + /** + * @see #setProgramLocation(Program, Address) + */ + public void setProgramLocation(ProgramLocation loc) { + setProgramLocation(loc.getProgram(), loc.getAddress()); + } + /** * Set the current program location * @@ -422,12 +429,13 @@ public class AssemblyDualTextField { * This may cause the construction of a new assembler, if one suitable for the given program's * language has not yet been built. * - * @param loc the location + * @param program the program + * @param address the non-null address */ - public void setProgramLocation(ProgramLocation loc) { - this.program = loc.getProgram(); - this.addr = loc.getAddress(); - this.existing = program.getListing().getInstructionAt(addr); + public void setProgramLocation(Program program, Address address) { + this.program = program; + this.address = Objects.requireNonNull(address); + this.existing = program.getListing().getInstructionAt(address); this.assembler = Assemblers.getAssembler(program); } @@ -438,9 +446,9 @@ public class AssemblyDualTextField { * @param lang the language * @param addr the address */ - public void setLanguageLocation(Language lang, long addr) { + public void setLanguageLocation(Language lang, Address addr) { this.program = null; - this.addr = lang.getDefaultSpace().getAddress(addr); + this.address = addr; this.existing = null; this.assembler = Assemblers.getAssembler(lang); @@ -514,7 +522,7 @@ public class AssemblyDualTextField { /** * Set the visibility of the text box(es) * - * @param visibility the VisibilityMode to set. + * @param visibility the {@link VisibilityMode} to set. */ public void setVisible(VisibilityMode visibility) { switch (visibility) { @@ -533,6 +541,35 @@ public class AssemblyDualTextField { } } + /** + * Get the visibility of the text box(es) + * + *

+ * NOTE: This method assumes nothing else changes the visibility of the text boxes. If + * anything else does, then it should be sure to maintain a configuration consistent with one of + * the {@link VisibilityMode}s. + * + * @return the current mode + */ + public VisibilityMode getVisible() { + if (linker.isVisible()) { + if (assembly.isVisible()) { + throw new AssertionError(); + } + else { + return VisibilityMode.DUAL_VISIBLE; + } + } + else { + if (assembly.isVisible()) { + return VisibilityMode.SINGLE_VISIBLE; + } + else { + return VisibilityMode.INVISIBLE; + } + } + } + /** * Set the font for all text fields * @@ -699,7 +736,7 @@ public class AssemblyDualTextField { * @return the collection of completion items */ protected Collection computeCompletions(String text) { - final AssemblyPatternBlock ctx = assembler.getContextAt(addr); + final AssemblyPatternBlock ctx = assembler.getContextAt(address); Set result = new TreeSet<>(); Collection parses = assembler.parseLine(text); @@ -720,7 +757,7 @@ public class AssemblyDualTextField { parses = assembler.parseLine(fullText); for (AssemblyParseResult parse : parses) { if (!parse.isError()) { - AssemblyResolutionResults sems = assembler.resolveTree(parse, addr); + AssemblyResolutionResults sems = assembler.resolveTree(parse, address); for (AssemblyResolution ar : sems) { if (ar.isError()) { //result.add(new AssemblyError("", ar.toString())); @@ -749,7 +786,7 @@ public class AssemblyDualTextField { public class AssemblyDualTextFieldDemo implements GhidraLaunchable { public final LanguageID DEMO_LANG_ID = new LanguageID("x86:LE:64:default"); public final String ADDR_FORMAT = "@%08x:"; - long curAddr = 0; + Address curAddr; @Override public void launch(GhidraApplicationLayout layout, String[] args) throws Exception { @@ -768,6 +805,7 @@ public class AssemblyDualTextField { SleighLanguageProvider provider = new SleighLanguageProvider(); SleighLanguage lang = (SleighLanguage) provider.getLanguage(DEMO_LANG_ID); + curAddr = lang.getDefaultSpace().getAddress(0); input.setLanguageLocation(lang, curAddr); @@ -788,7 +826,7 @@ public class AssemblyDualTextField { String data = NumericUtilities.convertBytesToString(ins.getData()); asm.setText(asm.getText() + data); input.clear(); - curAddr += ins.getData().length; + curAddr = curAddr.addWrap(ins.getData().length); input.setLanguageLocation(lang, curAddr); addrlabel.setText(String.format(ADDR_FORMAT, curAddr)); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java new file mode 100644 index 0000000000..5ce17b4cfb --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchDataAction.java @@ -0,0 +1,153 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.assembler; + +import java.awt.Color; +import java.awt.Font; +import java.awt.event.FocusListener; +import java.awt.event.KeyListener; + +import javax.swing.*; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.framework.plugintool.Plugin; +import ghidra.program.database.util.ProgramTransaction; +import ghidra.program.model.address.*; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +/** + * A context menu action to patch data at the current address + */ +public class PatchDataAction extends AbstractPatchAction { + private static final KeyStroke KEYBIND_PATCH_DATA = KeyStroke.getKeyStroke("ctrl shift H"); + + /*test*/ final JTextField input = new JTextField(); + + private Data data; + + public PatchDataAction(Plugin owner, String name) { + super(owner, name); + + setPopupMenuData(new MenuData(new String[] { "Patch Data" }, MENU_GROUP)); + setKeyBindingData(new KeyBindingData(KEYBIND_PATCH_DATA)); + setHelpLocation(new HelpLocation(owner.getName(), "patch_data")); + + input.setBorder(BorderFactory.createLineBorder(Color.RED, 2)); + + init(); + } + + @Override + protected void addInputFocusListener(FocusListener listener) { + input.addFocusListener(listener); + } + + @Override + protected void addInputKeyListener(KeyListener listener) { + input.addKeyListener(listener); + } + + @Override + protected boolean isApplicableToUnit(CodeUnit cu) { + if (!(cu instanceof Data)) { + return false; + } + Data data = (Data) cu; + if (!data.getBaseDataType().isEncodable()) { + return false; + } + return true; + } + + @Override + protected void prepare(CodeUnit unit) { + data = (Data) unit; + } + + @Override + protected void setInputFont(Font font) { + input.setFont(font); + } + + @Override + protected boolean showInputs(FieldPanel fieldPanel) { + FieldLocation locOpns = findFieldLocation(getAddress(), "Operands"); + if (locOpns == null) { + Msg.showError(this, fieldPanel, getName(), + "The Operands field must be present to patch data"); + return false; + } + fieldPanel.add(input, locOpns); + input.setVisible(true); + input.grabFocus(); + return true; + } + + @Override + protected void fillInputs(CodeUnit unit) { + String repr = data.getDefaultValueRepresentation(); + input.setText(repr); + input.setCaretPosition(repr.length()); + } + + @Override + public void accept() { + Program program = getProgram(); + Address address = getAddress(); + DataType dt = data.getBaseDataType(); + /** + * Do as much outside the transaction as possible. The tool tends to steal focus away upon + * restoring the database, and that causes the input fields to disappear. + */ + byte[] encoded; + AddressRange rng; + try { + encoded = dt.encodeRepresentation(input.getText(), data, data, data.getLength()); + rng = new AddressRangeImpl(address, encoded.length); + } + catch (DataTypeEncodeException | AddressOverflowException e) { + tool.setStatusInfo(e.getMessage(), true); + return; + } + try (ProgramTransaction trans = + ProgramTransaction.open(program, "Patch Data @" + address + ": " + input.getText())) { + int oldLength = data.getLength(); + if (encoded.length != oldLength) { + program.getListing().clearCodeUnits(address, rng.getMaxAddress(), false); + } + program.getMemory().setBytes(address, encoded); + if (encoded.length != oldLength) { + program.getListing().createData(address, dt, encoded.length); + } + trans.commit(); + hide(); + } + catch (MemoryAccessException e) { + Msg.showError(this, null, "Patch Failure", e.getMessage(), e); + } + catch (CodeUnitInsertionException | DataTypeConflictException e) { + throw new AssertionError(); // Should have been cleared first + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java new file mode 100644 index 0000000000..838a860012 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java @@ -0,0 +1,290 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.assembler; + +import java.awt.Color; +import java.awt.Font; +import java.awt.event.FocusListener; +import java.awt.event.KeyListener; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.KeyStroke; + +import org.apache.commons.collections4.map.DefaultedMap; +import org.apache.commons.collections4.map.LazyMap; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import docking.widgets.autocomplete.*; +import docking.widgets.fieldpanel.*; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.assembler.AssemblyDualTextField.*; +import ghidra.framework.plugintool.Plugin; +import ghidra.program.database.util.ProgramTransaction; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; +import ghidra.util.task.CachingSwingWorker; +import ghidra.util.task.TaskMonitor; + +/** + * A context menu action to assemble an instruction at the current address + */ +public class PatchInstructionAction extends AbstractPatchAction { + + /** + * Enumerated quality ratings and text to describe them. + */ + static enum AssemblyRating { + UNRATED("This processor has not been tested with the assembler." + + " The assembler will probably work on this language."), + POOR("This processor received a rating of POOR during testing." + + " We DO NOT recommend trying to assemble."), + BRONZE("This processor received a rating of BRONZE during testing." + + " A fair number of instructions may assemble, but we DO NOT recommend trying to" + + " assemble."), + SILVER("This processor received a rating of SILVER during testing." + + " Most instructions should work, but you will likely encounter a few errors."), + GOLD("This processor received a rating of GOLD during testing." + + " You should rarely encounter an error."), + PLATINUM("This processor received a rating of PLATINUM during testing."); + + final String message; + + private AssemblyRating(String message) { + this.message = message; + } + } + + // To build the assembler in the background if it takes a while + private static class AssemblerConstructorWorker extends CachingSwingWorker { + private Language language; + + public AssemblerConstructorWorker(Language language) { + super("Assemble", false); + this.language = language; + } + + @Override + protected Assembler runInBackground(TaskMonitor monitor) { + monitor.setMessage("Constructing assembler for " + language); + return Assemblers.getAssembler(language); + } + } + + private static final String ASSEMBLY_RATING = "assemblyRating"; + private static final String ASSEMBLY_MESSAGE = "assemblyMessage"; + private static final KeyStroke KEYBIND_PATCH_INSTRUCTION = + KeyStroke.getKeyStroke("ctrl shift G"); + + /** + * A listener for activation of a completion item + * + *

+ * The {@link AbstractPatchAction#accept()} method does not suffice for this action, since one + * of the suggested byte sequences must be selected, as presented by the completer. Thus, we'll + * nop that method, and instead call our own acceptance logic from here. + */ + private class ListenerForAccept implements AutocompletionListener { + @Override + public void completionActivated(AutocompletionEvent ev) { + if (ev.getSelection() instanceof AssemblyInstruction) { + AssemblyInstruction ins = (AssemblyInstruction) ev.getSelection(); + accept(ins); + } + } + } + + private final Map> cache = + LazyMap.lazyMap(new HashMap<>(), language -> new AssemblerConstructorWorker(language)); + + /*test*/ final Map shownWarning = + DefaultedMap.defaultedMap(new HashMap<>(), false); + + /*test*/ final AssemblyDualTextField input = new AssemblyDualTextField(); + private final ListenerForAccept listenerForAccept = new ListenerForAccept(); + + private Language language; + private Assembler assembler; + + // Callback to keep the autocompleter positioned under the fields + private FieldPanelOverLayoutListener listenerToMoveAutocompleter = ev -> { + TextFieldAutocompleter autocompleter = input.getAutocompleter(); + if (autocompleter.isCompletionListVisible()) { + autocompleter.updateDisplayLocation(); + } + }; + + public PatchInstructionAction(Plugin owner, String name) { + super(owner, name); + + setPopupMenuData(new MenuData(new String[] { "Patch Instruction" }, MENU_GROUP)); + setKeyBindingData(new KeyBindingData(KEYBIND_PATCH_INSTRUCTION)); + setHelpLocation(new HelpLocation(owner.getName(), "patch_instruction")); + + input.getMnemonicField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); + input.getOperandsField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); + input.getAssemblyField().setBorder(BorderFactory.createLineBorder(Color.RED, 2)); + + input.getAutocompleter().addAutocompletionListener(listenerForAccept); + + init(); + } + + @Override + public void dispose() { + super.dispose(); + input.dispose(); + } + + @Override + protected void addInputFocusListener(FocusListener listener) { + input.addFocusListener(listener); + } + + @Override + protected void addInputKeyListener(KeyListener listener) { + input.addKeyListener(listener); + } + + @Override + protected void addLayoutListeners(FieldPanelOverLayoutManager fieldLayoutManager) { + fieldLayoutManager.addLayoutListener(listenerToMoveAutocompleter); + } + + @Override + protected void removeLayoutListeners(FieldPanelOverLayoutManager fieldLayoutManager) { + fieldLayoutManager.removeLayoutListener(listenerToMoveAutocompleter); + } + + @Override + protected boolean isApplicableToUnit(CodeUnit cu) { + return true; + } + + @Override + protected void prepare(CodeUnit cu) { + language = cu.getProgram().getLanguage(); + AssemblyRating rating = AssemblyRating.valueOf( + language.getProperty(ASSEMBLY_RATING + ":" + language.getLanguageID(), + AssemblyRating.UNRATED.name())); + if (AssemblyRating.PLATINUM != rating) { + String message = language.getProperty(ASSEMBLY_MESSAGE + ":" + language.getLanguageID(), + rating.message); + if (!shownWarning.get(language)) { + Msg.showWarn(this, null, "Assembler Rating", + "

" + message + "

"); + shownWarning.put(language, true); + } + } + + cache.get(language).get(null); + assembler = Assemblers.getAssembler(cu.getProgram()); + } + + @Override + protected void setInputFont(Font font) { + input.setFont(font); + } + + @Override + protected boolean showInputs(FieldPanel fieldPanel) { + input.setProgramLocation(getProgram(), getAddress()); + FieldLocation locMnem = findFieldLocation(getAddress(), "Mnemonic"); + if (locMnem == null) { + Msg.showError(this, fieldPanel, getName(), + "The Mnemonic field must be present to patch instruction"); + return false; + } + FieldLocation locOpns = findFieldLocation(getAddress(), "Operands"); + if (locOpns == null) { + // Use the single-box variant + fieldPanel.add(input.getAssemblyField(), locMnem); + input.setVisible(VisibilityMode.SINGLE_VISIBLE); + } + else { + // Use the split variant + fieldPanel.add(input.getMnemonicField(), locMnem); + fieldPanel.add(input.getOperandsField(), locOpns); + input.setVisible(VisibilityMode.DUAL_VISIBLE); + } + return true; + } + + @Override + protected void fillInputs(CodeUnit unit) { + if (unit instanceof Instruction) { + Instruction ins = (Instruction) unit; + String instr = ins.toString(); + if (ins.isInDelaySlot()) { + assert instr.startsWith("_"); + instr = instr.substring(1).trim(); + } + input.setText(instr); + input.setCaretPosition(instr.length()); + if (input.getVisible() == VisibilityMode.SINGLE_VISIBLE) { + input.getAssemblyField().grabFocus(); + } + else if (instr.contains(" ")) { + input.getOperandsField().grabFocus(); + } + else { + input.getMnemonicField().grabFocus(); + } + } + else { + input.setText(""); + input.setCaretPosition(0); + if (input.getVisible() == VisibilityMode.SINGLE_VISIBLE) { + input.getAssemblyField().grabFocus(); + } + else { + input.getMnemonicField().grabFocus(); + } + } + } + + @Override + public void accept() { + // Do nothing. User must select a completion item instead + } + + /** + * Accept the given instruction selected by the user + * + * @param ins the selected instruction from the completion list + */ + public void accept(AssemblyInstruction ins) { + Program program = getProgram(); + Address address = getAddress(); + try (ProgramTransaction trans = + ProgramTransaction.open(program, "Assemble @" + address + ": " + input.getText())) { + assembler.patchProgram(ins.getData(), address); + trans.commit(); + hide(); + } + catch (MemoryAccessException e) { + Msg.showError(this, null, "Assemble", "Could not patch selected instruction", e); + } + } +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/assembler/AssemblerPluginTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/assembler/AssemblerPluginTest.java new file mode 100644 index 0000000000..8d485e7561 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/assembler/AssemblerPluginTest.java @@ -0,0 +1,258 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.assembler; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Objects; + +import javax.swing.JTextField; + +import org.junit.*; + +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyCompletion; +import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyInstruction; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin; +import ghidra.docking.settings.FormatSettingsDefinition; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramDB; +import ghidra.program.database.util.ProgramTransaction; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.ShortDataType; +import ghidra.program.model.data.TerminatedStringDataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.util.ProgramLocation; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.util.task.TaskMonitor; + +public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest { + protected TestEnv env; + protected PluginTool tool; + + private ProgramManagerPlugin programManager; + private AssemblerPlugin assemblerPlugin; + + private CodeViewerProvider codeViewer; + + private AssemblyDualTextField instructionInput; + private JTextField dataInput; + + private ProgramDB program; + private AddressSpace space; + private Memory memory; + private Listing listing; + + @Before + public void setUpAssemblerPluginTest() throws Exception { + env = new TestEnv(); + tool = env.getTool(); + + programManager = addPlugin(tool, ProgramManagerPlugin.class); + addPlugin(tool, CodeBrowserPlugin.class); + assemblerPlugin = addPlugin(tool, AssemblerPlugin.class); + + codeViewer = waitForComponentProvider(CodeViewerProvider.class); + + instructionInput = assemblerPlugin.patchInstructionAction.input; + dataInput = assemblerPlugin.patchDataAction.input; + + program = createDefaultProgram(getName(), "Toy:BE:64:default", this); + space = program.getAddressFactory().getDefaultAddressSpace(); + memory = program.getMemory(); + listing = program.getListing(); + + try (ProgramTransaction trans = ProgramTransaction.open(program, "Setup")) { + memory.createInitializedBlock(".text", space.getAddress(0x00400000), 0x1000, (byte) 0, + TaskMonitor.DUMMY, false); + trans.commit(); + } + + // Snuff the assembler's warning prompt + assemblerPlugin.patchInstructionAction.shownWarning.put(program.getLanguage(), true); + + env.showTool(); + programManager.openProgram(program); + } + + @After + public void tearDownAssemblerPluginTest() { + if (program != null) { + program.release(this); + } + } + + protected void assertDualFields() { + assertFalse(instructionInput.getAssemblyField().isVisible()); + assertTrue(instructionInput.getMnemonicField().isVisible()); + assertTrue(instructionInput.getOperandsField().isVisible()); + } + + protected List inputAndGetCompletions(String text) { + return runSwing(() -> { + instructionInput.setText(text); + instructionInput.auto.startCompletion(instructionInput.getOperandsField()); + instructionInput.auto.flushUpdates(); + return instructionInput.auto.getSuggestions(); + }); + } + + @Test + public void testActionPatchInstructionNoExisting() throws Exception { + Address address = space.getAddress(0x00400000); + codeViewer.goTo(program, new ProgramLocation(program, address)); + waitForSwing(); + + performAction(assemblerPlugin.patchInstructionAction, codeViewer, true); + assertDualFields(); + assertEquals("", instructionInput.getText()); + assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress()); + + List completions = inputAndGetCompletions("imm r0, #1234"); + AssemblyCompletion first = completions.get(0); + assertTrue(first instanceof AssemblyInstruction); + AssemblyInstruction ai = (AssemblyInstruction) first; + + runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai)); + waitForProgram(program); + + Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address)); + assertEquals("imm r0,#0x4d2", ins.toString()); + } + + @Test + public void testActionPatchInstructionExisting() throws Exception { + Address address = space.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(program); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Assemble pre-existing")) { + asm.assemble(address, "imm r0,#0x4d2"); + trans.commit(); + } + + codeViewer.goTo(program, new ProgramLocation(program, address)); + waitForSwing(); + + performAction(assemblerPlugin.patchInstructionAction, codeViewer, true); + assertDualFields(); + assertEquals("imm r0,#0x4d2", instructionInput.getText()); + assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress()); + + List completions = inputAndGetCompletions("imm r0, #123"); + AssemblyCompletion first = completions.get(0); + assertTrue(first instanceof AssemblyInstruction); + AssemblyInstruction ai = (AssemblyInstruction) first; + + runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai)); + waitForProgram(program); + + Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address)); + assertEquals("imm r0,#0x7b", ins.toString()); + } + + // TODO: Test disabled on uninitialized memory + // TODO: Test disabled on read-only listings + + protected Data doPatchAt(Address address, String expText, String newText) { + codeViewer.goTo(program, new ProgramLocation(program, address)); + waitForSwing(); + + performAction(assemblerPlugin.patchDataAction, codeViewer, true); + assertTrue(dataInput.isVisible()); + assertEquals(expText, dataInput.getText()); + assertEquals(address, assemblerPlugin.patchDataAction.getAddress()); + + runSwing(() -> { + dataInput.setText(newText); + assemblerPlugin.patchDataAction.accept(); + }); + waitForProgram(program); + + return Objects.requireNonNull(listing.getDataAt(address)); + } + + @Test + public void testActionPatchDataShortHexValid() throws Exception { + Address address = space.getAddress(0x00400000); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Place short")) { + listing.createData(address, ShortDataType.dataType); + trans.commit(); + } + + Data data = doPatchAt(address, "0h", "1234h"); + assertEquals("1234h", data.getDefaultValueRepresentation()); + } + + @Test + public void testActionPatchDataShortDecValid() throws Exception { + Address address = space.getAddress(0x00400000); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Place short")) { + Data data = listing.createData(address, ShortDataType.dataType); + FormatSettingsDefinition.DEF.setChoice(data, FormatSettingsDefinition.DECIMAL); + trans.commit(); + } + + Data data = doPatchAt(address, "0", "1234"); + assertEquals("1234", data.getDefaultValueRepresentation()); + } + + @Test + public void testActionPatchDataUTF8StringSameLength() throws Exception { + Address address = space.getAddress(0x00400000); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Place string")) { + memory.setBytes(address, "Hello, World!\0".getBytes("utf-8")); + listing.createData(address, TerminatedStringDataType.dataType); + trans.commit(); + } + + Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello, Patch!\""); + assertEquals("\"Hello, Patch!\"", data.getDefaultValueRepresentation()); + } + + @Test + public void testActionPatchDataUTF8StringShorter() throws Exception { + Address address = space.getAddress(0x00400000); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Place string")) { + memory.setBytes(address, "Hello, World!\0".getBytes("utf-8")); + listing.createData(address, TerminatedStringDataType.dataType); + trans.commit(); + } + + Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello!\""); + assertEquals("\"Hello!\"", data.getDefaultValueRepresentation()); + assertEquals(7, data.getLength()); + } + + @Test + public void testActionPatchDataUTF8StringLonger() throws Exception { + Address address = space.getAddress(0x00400000); + try (ProgramTransaction trans = ProgramTransaction.open(program, "Place string")) { + memory.setBytes(address, "Hello, World!\0".getBytes("utf-8")); + listing.createData(address, TerminatedStringDataType.dataType); + trans.commit(); + } + + Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello to you, too!\""); + assertEquals("\"Hello to you, too!\"", data.getDefaultValueRepresentation()); + assertEquals(19, data.getLength()); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java index ae6a08f322..d1a153a307 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java @@ -19,6 +19,8 @@ import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.swing.*; import javax.swing.border.BevelBorder; @@ -35,55 +37,59 @@ import ghidra.util.task.SwingUpdateManager; /** * An autocompleter that may be attached to one or more {@link JTextField}. * + *

* Each autocompleter instance has one associated window (displaying the list of suggestions) and * one associated model (generating the list of suggestions). Thus, the list can only be active on * one of the attached text fields at a time. This is usually the desired behavior, and it allows * for one autocompleter to be reused on many fields. Behavior is undefined when multiple - * autocompleters are attached to the same text field. More likely, you should implement a - * composite model if you wish to present completions from multiple models on a single text field. + * autocompleters are attached to the same text field. More likely, you should implement a composite + * model if you wish to present completions from multiple models on a single text field. * + *

* By default, the autocompleter is activated when the user presses CTRL-SPACE, at which point, the * model is queried for possible suggestions. The completer gives the model all the text preceding * the current field's caret. This behavior can be changed by overriding the - * {@link #getPrefix(JTextField)} method. This may be useful, e.g., to obtain a prefix for - * the current word, rather than the full field contents, preceding the caret. The list is - * displayed such that its top-left corner is placed directly under the current field's caret. As - * the user continues typing, the suggestions are re-computed, and the list tracks with the caret. - * This positioning behavior can be modified by overriding the - * {@link #getCompletionWindowPosition()} method. As a convenience, the - * {@link #getCaretPositionOnScreen(JTextField)} method is available to compute the default - * position. + * {@link #getPrefix(JTextField)} method. This may be useful, e.g., to obtain a prefix for the + * current word, rather than the full field contents, preceding the caret. The list is displayed + * such that its top-left corner is placed directly under the current field's caret. As the user + * continues typing, the suggestions are re-computed, and the list tracks with the caret. This + * positioning behavior can be modified by overriding the {@link #getCompletionWindowPosition()} + * method. As a convenience, the {@link #getCaretPositionOnScreen(JTextField)} method is available + * to compute the default position. * + *

* Whether or not the list is currently displayed, when the user presses CTRL-SPACE, if only one * completion is possible, it is automatically activated. This logic is applied again and again, * until either no suggestions are given, or more than one suggestion is given (or until the * autocompleter detects an infinite loop). This behavior can by modified on an item-by-item basis - * by overriding the {@link #getCompletionCanDefault(Object) getCompletionCanDefault(T)} method. This same behavior can be - * activated by calling the {@link #startCompletion(JTextField)} method, which may be useful, e.g., - * to bind a different key sequence to start autocompletion. + * by overriding the {@link #getCompletionCanDefault(Object) getCompletionCanDefault(T)} method. + * This same behavior can be activated by calling the {@link #startCompletion(JTextField)} method, + * which may be useful, e.g., to bind a different key sequence to start autocompletion. * + *

* The appearance of each item in the suggestion list can be modified by overriding the various * {@code getCompletion...} methods. Note that it's possible for an item to be displayed one way, * but cause the insertion of different text. In any case, it is best to ensure any modification * produces an intuitive behavior. * - * The simplest use case is to create a text field, create an autocompleter with a custom model, - * and then attach and show. + *

+ * The simplest use case is to create a text field, create an autocompleter with a custom model, and + * then attach and show. * - * *

  * JTextField field = new JTextField();
  * 
- * {@code AutocompletionModel model = new AutocompletionModel() }{
+ * AutocompletionModel model = new AutocompletionModel() {
  *     @Override
- *     {@code public Collection computeCompletions(String text)} {
- *         ... // Populate the completion list based on the given prefix.
+ *     public Collection computeCompletions(String text) {
+ *         // ... Populate the completion list based on the given prefix.
  *     }
  * }
- * {@code TextFieldAutocompleter completer = new TextFieldAutocompleter(model);
+ * TextFieldAutocompleter completer = new TextFieldAutocompleter(model);
  * completer.attachTo(field);
- * ... // Add the field to, e.g., a dialog, and show.
- * }
+ * // ... Add the field to, e.g., a dialog, and show. + * + * * * @param the type of suggestions presented by this autocompleter. */ @@ -261,6 +267,7 @@ public class TextFieldAutocompleter { /** * Create a new autocompleter associated with the given model. + * * @param model the model giving the suggestions. */ public TextFieldAutocompleter(AutocompletionModel model) { @@ -270,6 +277,7 @@ public class TextFieldAutocompleter { /** * Recompute the display location and move with list window. * + *

* This is useful, e.g., when the window containing the associated text field(s) moves. */ public void updateDisplayLocation() { @@ -281,6 +289,7 @@ public class TextFieldAutocompleter { /** * Update the contents of the suggestion list. * + *

* This entails taking the prefix, querying the model, and rendering the list. */ protected void updateDisplayContents() { @@ -288,7 +297,7 @@ public class TextFieldAutocompleter { updateManager.updateLater(); } - /* + /** * The actual implementation of updateDisplayContents, which gets scheduled asynchronously. */ private void doUpdateDisplayContents() { @@ -343,6 +352,7 @@ public class TextFieldAutocompleter { /** * Show or hide the completion list window + * * @param visible true to show, false to hide */ public void setCompletionListVisible(boolean visible) { @@ -364,7 +374,9 @@ public class TextFieldAutocompleter { /** * Check if the completion list window is visible. * + *

* If it is visible, this implies that the user is actively using the autocompleter. + * * @return true if shown, false if hidden. */ public boolean isCompletionListVisible() { @@ -397,11 +409,12 @@ public class TextFieldAutocompleter { /** * Get the preferred location (on screen) of the completion list window. * - * Typically, this is a location near the focused field. Ideally, it is positioned such that - * the displayed suggestions coincide with the applicable text in the focused field. For - * example, if the suggestions display some portion of the prefix, the window could be - * positioned such that the portion in the suggestion appears directly below the same portion - * in the field. + *

+ * Typically, this is a location near the focused field. Ideally, it is positioned such that the + * displayed suggestions coincide with the applicable text in the focused field. For example, if + * the suggestions display some portion of the prefix, the window could be positioned such that + * the portion in the suggestion appears directly below the same portion in the field. + * * @return the point giving the top-left corner of the completion window */ protected Point getCompletionWindowPosition() { @@ -411,17 +424,18 @@ public class TextFieldAutocompleter { /** * Get the preferred dimensions of the completion list window. * + *

* Typically, this is the width of the focused field. - * @return the dimension giving the preferred height and width. A value can be -1 to indicate - * no preference. + * + * @return the dimension giving the preferred height and width. A value can be -1 to indicate no + * preference. */ protected Dimension getDefaultCompletionWindowDimension() { return new Dimension(focus.getWidth(), -1); } /** - * A convenience function that returns the bottom on-screen position of the given field's - * caret. + * A convenience function that returns the bottom on-screen position of the given field's caret. * * @param field the field, typically the one having focus * @return the on-screen position of the caret's bottom. @@ -444,10 +458,13 @@ public class TextFieldAutocompleter { /** * Builds the list cell renderer for the autocompletion list. * + *

* A programmer may override this if the various {@code getCompletion...} methods prove * insufficient for customizing the display of the suggestions. Please remember that - * {@link JLabel}s can render HTML, so {@link #getCompletionDisplay(Object) getCompletionDisplay(T)} is quite powerful - * with the default {@link AutocompletionCellRenderer}. + * {@link JLabel}s can render HTML, so {@link #getCompletionDisplay(Object) + * getCompletionDisplay(T)} is quite powerful with the default + * {@link AutocompletionCellRenderer}. + * * @return a list cell renderer for the completion list. */ protected ListCellRenderer buildListCellRenderer() { @@ -457,7 +474,9 @@ public class TextFieldAutocompleter { /** * Attach the autocompleter to the given text field. * + *

* If this method is never called, then the autocompleter can never appear. + * * @param field the field that will gain this autocompletion feature * @return true, if this field is not already attached */ @@ -502,6 +521,7 @@ public class TextFieldAutocompleter { /** * Cause the currently-selected suggestion to be activated. * + *

* By default, this is called when the user presses ENTER or clicks a suggestion. */ protected void activateCurrentCompletion() { @@ -516,6 +536,7 @@ public class TextFieldAutocompleter { /** * Fire the registered autocompletion listeners on the given event. * + *

* Each registered listener is invoked in order of registration. If any listener consumes the * event, then later-registered listeners will not be notified of the event. If any listener * cancels the event, then the suggested text will not be inserted. @@ -539,8 +560,9 @@ public class TextFieldAutocompleter { return; } try { - focus.getDocument().insertString(focus.getCaretPosition(), getCompletionText(sel), - null); + focus.getDocument() + .insertString(focus.getCaretPosition(), getCompletionText(sel), + null); } catch (BadLocationException e) { throw new AssertionError("INTERNAL: Should not be here", e); @@ -549,6 +571,7 @@ public class TextFieldAutocompleter { /** * Register the given auto-completion listener + * * @param l the listener to register */ public void addAutocompletionListener(AutocompletionListener l) { @@ -557,6 +580,7 @@ public class TextFieldAutocompleter { /** * Unregister the given auto-completion listener + * * @param l the listener to unregister */ public void removeAutocompletionListener(AutocompletionListener l) { @@ -565,6 +589,7 @@ public class TextFieldAutocompleter { /** * Get all the registered auto-completion listeners + * * @return an array of registered listeners */ @SuppressWarnings("unchecked") @@ -576,7 +601,7 @@ public class TextFieldAutocompleter { * Get all registered listeners of the given type * * @param listenerType the type of listeners to get - * @return an array of registered listeners + * @return an array of registered listeners */ @SuppressWarnings({ "unchecked", "hiding" }) public T[] getListeners(Class listenerType) { @@ -660,12 +685,13 @@ public class TextFieldAutocompleter { /** * Decide whether the given suggestion can be automatically activated. * + *

* When autocompletion is started (via {@link #startCompletion(JTextField)}) or when the user * presses CTRL-SPACE, if there is only a single suggestion, it is taken automatically, and the - * process repeats until there is not a sole suggestion. Before the suggestion is taken, - * though, it calls this method. If it returns false, the single suggestion is displayed in a - * 1-long list instead. This is useful to prevent consequential actions from being - * automatically activated by the autocompleter. + * process repeats until there is not a sole suggestion. Before the suggestion is taken, though, + * it calls this method. If it returns false, the single suggestion is displayed in a 1-long + * list instead. This is useful to prevent consequential actions from being automatically + * activated by the autocompleter. * * @param sel the potentially auto-activated suggestion. * @return true to permit auto-activation, false to prevent it. @@ -677,13 +703,16 @@ public class TextFieldAutocompleter { /** * Starts the autocompleter on the given text field. * + *

* First, this repeatedly attempts auto-activation. When there are many suggestions, or when - * auto-activation is prevented (see {@link #getCompletionCanDefault(Object) getCompletionCanDefault(T)}), a list is displayed - * (usually below the caret) containing the suggestions given the fields current contents. The - * list remains open until either the user cancels it (usually via ESC) or the user activates - * a suggestion. + * auto-activation is prevented (see {@link #getCompletionCanDefault(Object) + * getCompletionCanDefault(T)}), a list is displayed (usually below the caret) containing the + * suggestions given the fields current contents. The list remains open until either the user + * cancels it (usually via ESC) or the user activates a suggestion. + * + *

+ * NOTE: The text field must already be attached. * - * NOTE: The text field must already be attached. * @param field the field on which to start autocompletion. */ public void startCompletion(JTextField field) { @@ -719,11 +748,19 @@ public class TextFieldAutocompleter { } } + /** + * If a completion list update is pending, run it immediately + */ + public void flushUpdates() { + updateManager.flush(); + } + /** * Cause the suggestion at the given index to be selected + * * @param index the index of the selection */ - protected void select(int index) { + public void select(int index) { list.setSelectedIndex(index); list.ensureIndexIsVisible(index); } @@ -731,7 +768,7 @@ public class TextFieldAutocompleter { /** * Cause the next suggestion to be selected, wrapping if applicable */ - protected void selectNext() { + public void selectNext() { int index = list.getSelectedIndex(); int size = listModel.getSize(); @@ -745,7 +782,7 @@ public class TextFieldAutocompleter { /** * Cause the previous suggestion to be selected, wrapping if applicable */ - protected void selectPrev() { + public void selectPrev() { int index = list.getSelectedIndex(); int size = listModel.getSize(); if (index >= 0) { @@ -794,17 +831,28 @@ public class TextFieldAutocompleter { /** * Select the first suggestion */ - protected void selectFirst() { + public void selectFirst() { select(0); } /** * Select the last suggestion */ - protected void selectLast() { + public void selectLast() { select(listModel.getSize() - 1); } + /** + * Get the list of suggestions as ordered on screen + * + * @return an immutable copy of the list + */ + public List getSuggestions() { + return IntStream.range(0, listModel.getSize()) + .mapToObj(listModel::get) + .collect(Collectors.toUnmodifiableList()); + } + /** * A listener to handle all the callbacks */ @@ -864,7 +912,7 @@ public class TextFieldAutocompleter { } } else if (e.getKeyCode() == KeyEvent.VK_SPACE && - (e.getModifiers() & InputEvent.CTRL_MASK) != 0) { + (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) { startCompletion((JTextField) e.getComponent()); e.consume(); } @@ -953,8 +1001,9 @@ public class TextFieldAutocompleter { /** * A demonstration of the autocompleter on a single text field. * - * The autocompleter offers the tails from a list of strings that start with the text before - * the caret. + *

+ * The autocompleter offers the tails from a list of strings that start with the text before the + * caret. */ public static class TextFieldAutocompleterDemo { public static void main(String[] args) { @@ -996,6 +1045,7 @@ public class TextFieldAutocompleter { /** * A demonstration of the autocompleter on two linked text fields. * + *

* This demo was designed to test whether the autocompleter and the {@link TextFieldLinker} * could be composed correctly. */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextFieldLinker.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextFieldLinker.java index 6bc9da7149..5b07f4e8b2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextFieldLinker.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/TextFieldLinker.java @@ -855,6 +855,15 @@ public class TextFieldLinker { } } + /** + * Check if all component fields are visible + * + * @return false if any component is not visible, true otherwise + */ + public boolean isVisible() { + return linkedFields.stream().allMatch(lf -> lf.field.isVisible()); + } + /** * Add a focus listener * diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java index fcc914ebf6..073a8fd20e 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java @@ -32,12 +32,15 @@ public class FormatSettingsDefinition implements EnumSettingsDefinition { protected static final String FORMAT = "format"; - public static final FormatSettingsDefinition DEF_CHAR = new FormatSettingsDefinition(CHAR); // Format with CHAR default - public static final FormatSettingsDefinition DEF_HEX = new FormatSettingsDefinition(HEX); // Format with HEX default + // Definitions with each settings as a default + public static final FormatSettingsDefinition DEF_HEX = new FormatSettingsDefinition(HEX); public static final FormatSettingsDefinition DEF_DECIMAL = - new FormatSettingsDefinition(DECIMAL); // Format with DECIMAL default + new FormatSettingsDefinition(DECIMAL); + public static final FormatSettingsDefinition DEF_BINARY = new FormatSettingsDefinition(BINARY); + public static final FormatSettingsDefinition DEF_OCTAL = new FormatSettingsDefinition(OCTAL); + public static final FormatSettingsDefinition DEF_CHAR = new FormatSettingsDefinition(CHAR); - public static final FormatSettingsDefinition DEF = DEF_HEX; // Format with HEX default + public static final FormatSettingsDefinition DEF = DEF_HEX; // Default is HEX private final int defaultFormat; @@ -47,6 +50,7 @@ public class FormatSettingsDefinition implements EnumSettingsDefinition { /** * Returns the format based on the specified settings + * * @param settings the instance settings or null for default value. * @return the format value (HEX, DECIMAL, BINARY, OCTAL, CHAR) */ @@ -66,8 +70,8 @@ public class FormatSettingsDefinition implements EnumSettingsDefinition { } /** - * Returns the numeric radix associated with the - * format identified by the specified settings. + * Returns the numeric radix associated with the format identified by the specified settings. + * * @param settings the instance settings. * @return the format radix */ @@ -142,6 +146,7 @@ public class FormatSettingsDefinition implements EnumSettingsDefinition { /** * Sets the settings object to the enum value indicating the specified choice as a string. + * * @param settings the settings to store the value. * @param choice enum string representing a choice in the enum. */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractIntegerDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractIntegerDataType.java index a1fcbe3cfc..1d2c24624f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractIntegerDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractIntegerDataType.java @@ -188,16 +188,52 @@ public abstract class AbstractIntegerDataType extends BuiltIn implements ArraySt return new Scalar(size * 8, val, isSigned()); } + /** + * Get the number of bits in the integral type + * + * @param type the type + * @return the number of bits + */ + protected static int getBitCount(Class type) { + if (type == Byte.class) { + return Byte.SIZE; + } + if (type == Short.class) { + return Short.SIZE; + } + if (type == Integer.class) { + return Integer.SIZE; + } + if (type == Long.class) { + return Long.SIZE; + } + throw new AssertionError(); + } + protected BigInteger castValueToEncode(Object value) throws DataTypeEncodeException { if (value instanceof BigInteger) { return (BigInteger) value; } if (value instanceof Scalar) { + // I'll take the scalar's signedness and neglect this type's.... return ((Scalar) value).getBigInteger(); } - if (value instanceof Byte || value instanceof Short || value instanceof Character || - value instanceof Integer || value instanceof Long) { - return BigInteger.valueOf(((Number) value).longValue()); + if (value instanceof Character) { + int numeric = Character.getNumericValue((Character) value); + if (numeric < 0) { + throw new DataTypeEncodeException("Character cannot be converted to number", value, + this); + } + return BigInteger.valueOf(numeric); + } + if (value instanceof Byte || value instanceof Short || value instanceof Integer || + value instanceof Long) { + Number number = (Number) value; + BigInteger signedVal = BigInteger.valueOf(number.longValue()); + if (isSigned() || signedVal.signum() >= 0) { + return signedVal; + } + return signedVal.add(BigInteger.ONE.shiftLeft(getBitCount(number.getClass()))); } throw new DataTypeEncodeException("Unsupported value type", value, this); } @@ -217,11 +253,21 @@ public abstract class AbstractIntegerDataType extends BuiltIn implements ArraySt throw new DataTypeEncodeException("Length mismatch", value, this); } BigInteger bigValue = castValueToEncode(value); - byte[] encoding = Utils.bigIntegerToBytes(bigValue, length, isSigned()); - if (!ENDIAN.isBigEndian(settings, buf)) { - ArrayUtilities.reverse(encoding); + if (bigValue.signum() == -1 && !isSigned()) { + throw new DataTypeEncodeException("Unsigned type cannot have negative value", value, + this); } - return encoding; + BigInteger maxValueExclusive = BigInteger.ONE.shiftLeft(length * 8 - (isSigned() ? 1 : 0)); + BigInteger minValueInclusive = isSigned() + ? BigInteger.ONE.shiftLeft(length * 8 - 1).negate() + : BigInteger.ZERO; + if (bigValue.compareTo(maxValueExclusive) >= 0) { + throw new DataTypeEncodeException("Value is too large", bigValue, this); + } + if (minValueInclusive.compareTo(bigValue) > 0) { + throw new DataTypeEncodeException("Value is too small", bigValue, this); + } + return Utils.bigIntegerToBytes(bigValue, length, ENDIAN.isBigEndian(settings, buf)); } @Override @@ -337,12 +383,15 @@ public abstract class AbstractIntegerDataType extends BuiltIn implements ArraySt case FormatSettingsDefinition.DECIMAL: radix = 10; suffix = ""; + break; case FormatSettingsDefinition.BINARY: radix = 2; suffix = "b"; + break; case FormatSettingsDefinition.OCTAL: radix = 8; suffix = "o"; + break; default: throw new AssertionError(); } @@ -356,6 +405,21 @@ public abstract class AbstractIntegerDataType extends BuiltIn implements ArraySt catch (Exception e) { throw new DataTypeEncodeException(repr, this, e); } + + /** + * Ghidra doesn't actually heed signedness unless the format is DECIMAL. Thus, for user + * input, and to make this an inverse of getRepresentation, we'll adjust values between SMAX + * and UMAX to ensure they get encoded as expected, rather than rejected. We'll still accept + * signed values, though, since the user would rightly expect those to work, even though + * it'll get echoed back in unsigned form. + */ + if (format != FormatSettingsDefinition.DECIMAL && isSigned()) { + BigInteger umax = BigInteger.ONE.shiftLeft(8 * length); + BigInteger smax = umax.shiftRight(1); + if (smax.compareTo(value) <= 0 && value.compareTo(umax) < 0) { + value = value.subtract(umax); + } + } return encodeValue(value, buf, settings, length); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeEncodeException.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeEncodeException.java index 97c363825c..9799194b4a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeEncodeException.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeEncodeException.java @@ -25,6 +25,28 @@ public class DataTypeEncodeException extends UsrException { private final Object value; private final DataType dt; + private static String computeMessage(String message, Object value, DataType dt, + Throwable cause) { + if (cause != null) { + String encodeError = "while encoding '" + value + "' for " + dt.getDisplayName(); + if (message != null) { + return cause.getMessage() + " (" + encodeError + ": " + message + ")"; + } + else { + return cause.getMessage() + "(" + encodeError + ")"; + } + } + else { + String encodeError = "Cannot encode '" + value + "' for " + dt.getDisplayName(); + if (message != null) { + return encodeError + ": " + message; + } + else { + return encodeError; + } + } + } + /** * Constructor * @@ -45,8 +67,7 @@ public class DataTypeEncodeException extends UsrException { * @param cause the exception cause */ public DataTypeEncodeException(String message, Object value, DataType dt, Throwable cause) { - super("Cannot encode '" + value + "' for " + dt.getDisplayName() + - (message == null ? "" : ": " + message), cause); + super(computeMessage(message, value, dt, cause), cause); this.value = value; this.dt = dt; } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/IntegerDataTypeTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/IntegerDataTypeTest.java new file mode 100644 index 0000000000..966437d687 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/IntegerDataTypeTest.java @@ -0,0 +1,224 @@ +/* ### + * 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.model.data; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.docking.settings.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.program.model.mem.MemBuffer; + +public class IntegerDataTypeTest extends AbstractGenericTest { + + private static byte[] arr(int... vals) { + byte[] result = new byte[vals.length]; + for (int i = 0; i < vals.length; i++) { + result[i] = (byte) vals[i]; + } + return result; + } + + private static MemBuffer buf(boolean bigEndian, int... vals) { + return new ByteMemBufferImpl(Address.NO_ADDRESS, arr(vals), bigEndian); + } + + private static Settings format(FormatSettingsDefinition setDef) { + Settings settings = new SettingsImpl(); + setDef.setChoice(settings, setDef.getChoice(null)); + return settings; + } + + // NB. Need at least one byte to appear "initialized" + private static final MemBuffer BE = buf(true, 0); + private static final MemBuffer LE = buf(false, 0); + + private static final Settings HEX = format(FormatSettingsDefinition.DEF_HEX); + private static final Settings DEC = format(FormatSettingsDefinition.DEF_DECIMAL); + private static final Settings BIN = format(FormatSettingsDefinition.DEF_BINARY); + private static final Settings OCT = format(FormatSettingsDefinition.DEF_OCTAL); + private static final Settings CHR = format(FormatSettingsDefinition.DEF_CHAR); + + private interface EncodeRunnable { + void run() throws Exception; + } + + private static void assertFails(EncodeRunnable r) throws Exception { + try { + r.run(); + } + catch (DataTypeEncodeException e) { + return; // pass + } + fail(); + } + + @Test + public void testEncodeValueUnsignedByteBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(1, null); + + // Technically, these two are exactly the same test, just different Java syntax + assertArrayEquals(arr(0xff), type.encodeValue((byte) 0xff, BE, HEX, 1)); + assertArrayEquals(arr(0xff), type.encodeValue((byte) -1, BE, HEX, 1)); + + assertFails(() -> type.encodeValue((short) 0x100, BE, HEX, 1)); + assertFails(() -> type.encodeValue((short) -1, BE, HEX, 1)); + + assertArrayEquals(arr(0xff), type.encodeValue(0xff, BE, HEX, 1)); + // This fails, because (int)-1 is 4294967295 when treated unsigned + assertFails(() -> type.encodeValue(-1, BE, HEX, 1)); + } + + @Test + public void testEncodeRepresentationUnsignedByteHexBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(1, null); + + // Sanity check: Renders unsigned + assertEquals("80h", type.getRepresentation(buf(true, 0x80), HEX, 1)); + + assertArrayEquals(arr(0x00), type.encodeRepresentation("0h", BE, HEX, 1)); + assertArrayEquals(arr(0x7f), type.encodeRepresentation("7fh", BE, HEX, 1)); + assertArrayEquals(arr(0x80), type.encodeRepresentation("80h", BE, HEX, 1)); + assertArrayEquals(arr(0xff), type.encodeRepresentation("ffh", BE, HEX, 1)); + + assertFails(() -> type.encodeRepresentation("100h", BE, HEX, 1)); + assertFails(() -> type.encodeRepresentation("-1h", BE, HEX, 1)); + } + + @Test + public void testEncodeRepresentationSignedShortHexBE() throws Exception { + DataType type = AbstractIntegerDataType.getSignedDataType(2, null); + + // Sanity check: Negative hex values render unsigned + assertEquals("8000h", type.getRepresentation(buf(true, 0x80, 0x00), HEX, 2)); + + assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2)); + assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2)); + assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2)); + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2)); + + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1h", BE, HEX, 2)); + assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("-8000h", BE, HEX, 2)); + + assertFails(() -> type.encodeRepresentation("10000h", BE, HEX, 2)); + assertFails(() -> type.encodeRepresentation("-8001h", BE, HEX, 2)); + } + + @Test + public void testEncodeRepresentationSignedShortHexLE() throws Exception { + DataType type = AbstractIntegerDataType.getSignedDataType(2, null); + + // Sanity check: Negative hex values render unsigned + assertEquals("8000h", type.getRepresentation(buf(false, 0x00, 0x80), HEX, 2)); + + assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", LE, HEX, 2)); + assertArrayEquals(arr(0xff, 0x7f), type.encodeRepresentation("7fffh", LE, HEX, 2)); + assertArrayEquals(arr(0x00, 0x80), type.encodeRepresentation("8000h", LE, HEX, 2)); + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", LE, HEX, 2)); + + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1h", LE, HEX, 2)); + assertArrayEquals(arr(0x00, 0x80), type.encodeRepresentation("-8000h", LE, HEX, 2)); + + assertFails(() -> type.encodeRepresentation("10000h", LE, HEX, 2)); + assertFails(() -> type.encodeRepresentation("-8001h", LE, HEX, 2)); + } + + @Test + public void testEncodeRepresentationUnsignedShortHexBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(2, null); + + // Sanity check: Renders unsigned + assertEquals("8000h", type.getRepresentation(buf(true, 0x80, 0x00), HEX, 2)); + + assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0h", BE, HEX, 2)); + assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("7fffh", BE, HEX, 2)); + assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("8000h", BE, HEX, 2)); + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("ffffh", BE, HEX, 2)); + + assertFails(() -> type.encodeRepresentation("-1h", BE, HEX, 2)); + assertFails(() -> type.encodeRepresentation("-8000h", BE, HEX, 2)); + assertFails(() -> type.encodeRepresentation("10000h", BE, HEX, 2)); + assertFails(() -> type.encodeRepresentation("-8001h", BE, HEX, 2)); + } + + @Test + public void testEncodeRepresentationSignedShortDecBE() throws Exception { + DataType type = AbstractIntegerDataType.getSignedDataType(2, null); + + // Sanity check: Negative hex values render signed + assertEquals("-32768", type.getRepresentation(buf(true, 0x80, 0x00), DEC, 2)); + + assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2)); + assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2)); + assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("-32768", BE, DEC, 2)); + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("-1", BE, DEC, 2)); + + assertFails(() -> type.encodeRepresentation("32768", BE, DEC, 2)); + assertFails(() -> type.encodeRepresentation("-32769", BE, DEC, 2)); + } + + @Test + public void testEncodeRepresentationUnsignedShortDecBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(2, null); + + // Sanity check: Renders unsigned + assertEquals("32768", type.getRepresentation(buf(true, 0x80, 0x00), DEC, 2)); + + assertArrayEquals(arr(0x00, 0x00), type.encodeRepresentation("0", BE, DEC, 2)); + assertArrayEquals(arr(0x7f, 0xff), type.encodeRepresentation("32767", BE, DEC, 2)); + assertArrayEquals(arr(0x80, 0x00), type.encodeRepresentation("32768", BE, DEC, 2)); + assertArrayEquals(arr(0xff, 0xff), type.encodeRepresentation("65535", BE, DEC, 2)); + + assertFails(() -> type.encodeRepresentation("-1", BE, DEC, 2)); + assertFails(() -> type.encodeRepresentation("65536", BE, DEC, 2)); + } + + @Test + public void testEncodeRepresentationSignedShortBinBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(2, null); + + // Sanity check + assertEquals("100000011b", type.getRepresentation(buf(true, 0x01, 0x03), BIN, 2)); + + assertArrayEquals(arr(0x01, 0x03), type.encodeRepresentation("100000011b", BE, BIN, 2)); + } + + @Test + public void testEncodeRepresentationSignedShortOctBE() throws Exception { + DataType type = AbstractIntegerDataType.getUnsignedDataType(2, null); + + // Sanity check + assertEquals("403o", type.getRepresentation(buf(true, 0x01, 0x03), OCT, 2)); + + assertArrayEquals(arr(0x01, 0x03), type.encodeRepresentation("403o", BE, OCT, 2)); + } + + @Test + public void testEncodeRepresentationChar() throws Exception { + DataType stype = AbstractIntegerDataType.getSignedDataType(1, null); + DataType utype = AbstractIntegerDataType.getUnsignedDataType(1, null); + + // Sanity check + assertEquals("'A'", stype.getRepresentation(buf(true, 0x41), CHR, 1)); + assertEquals("'A'", utype.getRepresentation(buf(true, 0x41), CHR, 1)); + + assertArrayEquals(arr(0x41), stype.encodeRepresentation("'A'", BE, CHR, 1)); + assertArrayEquals(arr(0x41), utype.encodeRepresentation("'A'", BE, CHR, 1)); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/ghidra/app/plugin/core/assembler/AssemblerPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/ghidra/app/plugin/core/assembler/AssemblerPluginScreenShots.java new file mode 100644 index 0000000000..36f1ed7c8d --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/screen/java/ghidra/app/plugin/core/assembler/AssemblerPluginScreenShots.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.assembler; + +import org.junit.Test; + +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import help.screenshot.GhidraScreenShotGenerator; + +public class AssemblerPluginScreenShots extends GhidraScreenShotGenerator { + @Test + public void testCaptureAssembler() throws Exception { + setToolSize(1000, 800); + + positionListingTop(0x00405120); + positionCursor(0x0040512e); + AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class); + CodeViewerProvider codeViewer = waitForComponentProvider(CodeViewerProvider.class); + + performAction(assemblerPlugin.patchInstructionAction, codeViewer, true); + + AssemblyDualTextField input = assemblerPlugin.patchInstructionAction.input; + runSwing(() -> { + input.auto.startCompletion(input.getOperandsField()); + input.auto.flushUpdates(); + input.auto.select(0); + }); + + captureProviderWithScreenShot(codeViewer); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AssemblerPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AssemblerPluginScreenShots.java deleted file mode 100644 index 703c8263e4..0000000000 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AssemblerPluginScreenShots.java +++ /dev/null @@ -1,66 +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 help.screenshot; - -import java.awt.AWTException; -import java.awt.Robot; -import java.awt.event.KeyEvent; - -import org.junit.Test; - -import docking.action.DockingActionIf; -import ghidra.app.plugin.core.assembler.AssemblerPlugin; -import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.framework.plugintool.util.PluginException; - -public class AssemblerPluginScreenShots extends GhidraScreenShotGenerator { - @Test - public void testCaptureAssembler() throws PluginException, AWTException, InterruptedException { - setToolSize(1000, 800); - - positionListingTop(0x00405120); - positionCursor(0x0040512e); - tool.addPlugin(AssemblerPlugin.class.getName()); - - DockingActionIf action = getAction(tool, "AssemblerPlugin", "Assemble"); - - performAction(action, true); - - Robot rob = new Robot(); - rob.keyPress(KeyEvent.VK_RIGHT); - rob.keyRelease(KeyEvent.VK_RIGHT); - - // TODO: Will this work on Mac? control vs command - rob.keyPress(KeyEvent.VK_CONTROL); - rob.keyPress(KeyEvent.VK_SPACE); - rob.keyRelease(KeyEvent.VK_SPACE); - rob.keyRelease(KeyEvent.VK_CONTROL); - - Thread.sleep(100); - - rob.keyPress(KeyEvent.VK_ESCAPE); - rob.keyRelease(KeyEvent.VK_ESCAPE); - - Thread.sleep(100); - - rob.keyPress(KeyEvent.VK_CONTROL); - rob.keyPress(KeyEvent.VK_SPACE); - rob.keyRelease(KeyEvent.VK_SPACE); - rob.keyRelease(KeyEvent.VK_CONTROL); - - captureProvider(CodeViewerProvider.class); - } -}