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 @@
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.
+ +There are two actions: One for instructions and one for data.
+ +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:
+ - -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.
+ +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 c7720e756e..7e05dfe03a 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/images/Assembler.png and b/Ghidra/Features/Base/src/main/help/help/topics/AssemblerPlugin/images/Assembler.png differ 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 " + message + "
+ * 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
+ * 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 " + message + "
* 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.
*
- *
*
* 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
* This entails taking the prefix, querying the model, and rendering the list.
*/
protected void updateDisplayContents() {
@@ -288,7 +297,7 @@ public class TextFieldAutocompleter
* 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
+ * 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
* 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
* 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 super T> buildListCellRenderer() {
@@ -457,7 +474,9 @@ public class TextFieldAutocompleter
* 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
* By default, this is called when the user presses ENTER or clicks a suggestion.
*/
protected void activateCurrentCompletion() {
@@ -516,6 +536,7 @@ public class TextFieldAutocompleter
* 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
* 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
* 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
+ * 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
* 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 extends Number> 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);
- }
-}
* JTextField field = new JTextField();
*
- * {@code AutocompletionModel
+ * // ... Add the field to, e.g., a dialog, and show.
+ *
+ *
*
* @param