mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 03:09:36 +08:00
Merge remote-tracking branch 'origin/GP-1684_Dan_patchDataAction--SQUASHED'
This commit is contained in:
@@ -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|
|
||||
|
||||
@@ -9,109 +9,99 @@
|
||||
|
||||
<TITLE>Assembler</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
<META name="generator" content="Microsoft FrontPage 4.0">
|
||||
<STYLE>
|
||||
<META name="generator" content="Microsoft FrontPage 4.0"><STYLE type="text/css">
|
||||
ol.ratings li {
|
||||
margin: 0 0 10px 0;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
|
||||
<BODY>
|
||||
<H1><A name="AssembleAction"></A>Assembler</H1>
|
||||
<H1><A name="plugin"></A>Assembler</H1>
|
||||
|
||||
<P>The <B>Patch Instruction</B> 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.</P>
|
||||
|
||||
<P align="center"><IMG border="1" src="images/Assembler.png"></P>
|
||||
<P align="center"><IMG alt="" border="1" src="images/Assembler.png"></P>
|
||||
|
||||
<P align="center">Assembly Editor</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>To edit assembly, select <B>Patch Instruction</B> from the
|
||||
<A href="../CodeBrowserPlugin/CodeBrowser.htm#Code_Browser">Listing View</A> 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.</P>
|
||||
</BLOCKQUOTE>
|
||||
<P>The Assembler plugin provides actions for modifying program bytes by inputing mnemonic
|
||||
assembly and data values.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>There are two actions: One for instructions and one for data.</P>
|
||||
|
||||
<H3><A name="patch_instruction">Patch Instruction</A></H3>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<P>Ghidra's assembler is based on the same SLEIGH modeling that powers the disassembler. This
|
||||
offers some nice benefits:</P>
|
||||
|
||||
|
||||
<UL>
|
||||
<LI>There is no need for an external tool chain.
|
||||
<LI>The assembler and disassembler share the same mnemonic syntax.
|
||||
<LI>Most Ghidra-supported processors are also supported by the assembler.
|
||||
<LI>Processors added to Ghidra automatically get an assembler.
|
||||
<LI>There is no need for an external tool chain.</LI>
|
||||
|
||||
<LI>The assembler and disassembler share the same mnemonic syntax.</LI>
|
||||
|
||||
<LI>Most Ghidra-supported processors are also supported by the assembler.</LI>
|
||||
|
||||
<LI>Processors added to Ghidra automatically get an assembler (most of the time).</LI>
|
||||
</UL>
|
||||
|
||||
<P>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:</P>
|
||||
|
||||
|
||||
<P>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:</P>
|
||||
|
||||
<OL class="ratings">
|
||||
<LI><B>Platinum:</B> Our automated tests did not find any errors. This offers the best
|
||||
possible user experience.</LI>
|
||||
<LI><B>Gold:</B> You will rarely encounter an error. You will find it very useful.</LI>
|
||||
<LI><B>Silver:</B> You may occasionally encounter an error, but the assembler is still
|
||||
usable. You will likely find it useful with occasional frustration.</LI>
|
||||
<LI><B>Bronze:</B> 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.</LI>
|
||||
<LI><B>Poor:</B> 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.</LI>
|
||||
<LI><B>Unrated:</B> 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.</LI>
|
||||
|
||||
<LI><B>Gold:</B> Our automated tests found a couple of small errors. You should rarely
|
||||
encounter issues.</LI>
|
||||
|
||||
<LI><B>Silver:</B> 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.</LI>
|
||||
|
||||
<LI><B>Bronze:</B> 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.</LI>
|
||||
|
||||
<LI><B>Poor:</B> 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.</LI>
|
||||
|
||||
<LI><B>Unrated:</B> 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.</LI>
|
||||
</OL>
|
||||
|
||||
<P>As of this release, our tested processors fall under Platinum, Gold, or Poor.</P>
|
||||
|
||||
<UL>
|
||||
<LI>Platinum</LI>
|
||||
<UL>
|
||||
<LI>68000:BE:32:default</LI>
|
||||
<LI>AARCH64:BE:64:v8A</LI>
|
||||
<LI>AARCH64:LE:64:v8A</LI>
|
||||
<LI>ARM:BE:32:v7</LI>
|
||||
<LI>ARM:LE:32:v7</LI>
|
||||
<LI>avr8:LE:16:extended</LI>
|
||||
<LI>MIPS:BE:32:default</LI>
|
||||
<LI>MIPS:BE:32:micro</LI>
|
||||
<LI>MIPS:BE:32:R6</LI>
|
||||
<LI>MIPS:BE:64:64-32addr</LI>
|
||||
<LI>MIPS:BE:64:R6</LI>
|
||||
<LI>MIPS:BE:64:default</LI>
|
||||
<LI>pa-risc:BE:32:default</LI>
|
||||
<LI>PowerPC:BE:32:default</LI>
|
||||
<LI>PowerPC:BE:64:A2-32addr</LI>
|
||||
<LI>PowerPC:BE:64:A2ALT-32addr</LI>
|
||||
<LI>PowerPC:BE:64:default</LI>
|
||||
<LI>sparc:BE:32:default</LI>
|
||||
<LI>sparc:BE:64:default</LI>
|
||||
<LI>SuperH4:BE:32:default</LI>
|
||||
<LI>SuperH4:LE:32:default</LI>
|
||||
<LI>TI_MSP430X:LE:32:default</LI>
|
||||
<LI>x86:LE:32:default</LI>
|
||||
</UL>
|
||||
<LI>Gold</LI>
|
||||
<UL>
|
||||
<LI>x86:LE:64:default</LI>
|
||||
</UL>
|
||||
<LI>Bronze</LI>
|
||||
<UL>
|
||||
<LI>avr32:BE:32:default</LI>
|
||||
</UL>
|
||||
<LI>Poor</LI>
|
||||
<UL>
|
||||
<LI>dsPIC30F:LE:24:default</LI>
|
||||
</UL>
|
||||
</UL>
|
||||
|
||||
<P>If the current processor scored anything less than <B>Platinum</B> you will receive a
|
||||
friendly warning reminding you what to expect.</P>
|
||||
|
||||
<H3><A name="patch_data"></A>Patch Data</H3>
|
||||
|
||||
<P>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. <B>NOTE:</B> 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.</P>
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Assembler</I> plugin</P>
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 76 KiB |
+384
@@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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)
|
||||
*
|
||||
* <p>
|
||||
* 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)
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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)
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* This hides the input field(s) without completing the action.
|
||||
*/
|
||||
public void cancel() {
|
||||
hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a listing field by name and address
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
-403
@@ -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<Language, CachingSwingWorker<Assembler>> cache =
|
||||
LazyMap.lazyMap(new HashMap<>(), language -> new AssemblerConstructorWorker(language));
|
||||
|
||||
private Map<Language, Boolean> 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<AssemblyCompletion> 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<Assembler> {
|
||||
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<AssemblyCompletion>, KeyListener {
|
||||
@Override
|
||||
public void completionActivated(AutocompletionEvent<AssemblyCompletion> 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",
|
||||
"<html><body><p style='width: 300px;'>" + message + "</p></body></html>");
|
||||
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;
|
||||
}
|
||||
}
|
||||
+25
-19
@@ -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.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-13
@@ -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)
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<AssemblyCompletion> computeCompletions(String text) {
|
||||
final AssemblyPatternBlock ctx = assembler.getContextAt(addr);
|
||||
final AssemblyPatternBlock ctx = assembler.getContextAt(address);
|
||||
|
||||
Set<AssemblyCompletion> result = new TreeSet<>();
|
||||
Collection<AssemblyParseResult> 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));
|
||||
}
|
||||
|
||||
+153
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+290
@@ -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<Assembler> {
|
||||
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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyCompletion> {
|
||||
@Override
|
||||
public void completionActivated(AutocompletionEvent<AssemblyCompletion> ev) {
|
||||
if (ev.getSelection() instanceof AssemblyInstruction) {
|
||||
AssemblyInstruction ins = (AssemblyInstruction) ev.getSelection();
|
||||
accept(ins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Language, CachingSwingWorker<Assembler>> cache =
|
||||
LazyMap.lazyMap(new HashMap<>(), language -> new AssemblerConstructorWorker(language));
|
||||
|
||||
/*test*/ final Map<Language, Boolean> 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<AssemblyCompletion> 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",
|
||||
"<html><body><p style='width: 300px;'>" + message + "</p></body></html>");
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+258
@@ -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<AssemblyCompletion> 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<AssemblyCompletion> 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<AssemblyCompletion> 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());
|
||||
}
|
||||
}
|
||||
+104
-54
@@ -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}.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* The simplest use case is to create a text field, create an autocompleter with a custom model, and
|
||||
* then attach and show.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* JTextField field = new JTextField();
|
||||
*
|
||||
* {@code AutocompletionModel<String> model = new AutocompletionModel<String>() }{
|
||||
* AutocompletionModel<String> model = new AutocompletionModel<String>() {
|
||||
* @Override
|
||||
* {@code public Collection<String> computeCompletions(String text)} {
|
||||
* ... // Populate the completion list based on the given prefix.
|
||||
* public Collection<String> computeCompletions(String text) {
|
||||
* // ... Populate the completion list based on the given prefix.
|
||||
* }
|
||||
* }
|
||||
* {@code TextFieldAutocompleter<String> completer = new TextFieldAutocompleter<String>(model);
|
||||
* TextFieldAutocompleter<String> completer = new TextFieldAutocompleter<String>(model);
|
||||
* completer.attachTo(field);
|
||||
* ... // Add the field to, e.g., a dialog, and show.
|
||||
* }</pre>
|
||||
* // ... Add the field to, e.g., a dialog, and show.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the type of suggestions presented by this autocompleter.
|
||||
*/
|
||||
@@ -261,6 +267,7 @@ public class TextFieldAutocompleter<T> {
|
||||
|
||||
/**
|
||||
* Create a new autocompleter associated with the given model.
|
||||
*
|
||||
* @param model the model giving the suggestions.
|
||||
*/
|
||||
public TextFieldAutocompleter(AutocompletionModel<T> model) {
|
||||
@@ -270,6 +277,7 @@ public class TextFieldAutocompleter<T> {
|
||||
/**
|
||||
* Recompute the display location and move with list window.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Update the contents of the suggestion list.
|
||||
*
|
||||
* <p>
|
||||
* This entails taking the prefix, querying the model, and rendering the list.
|
||||
*/
|
||||
protected void updateDisplayContents() {
|
||||
@@ -288,7 +297,7 @@ public class TextFieldAutocompleter<T> {
|
||||
updateManager.updateLater();
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* The actual implementation of updateDisplayContents, which gets scheduled asynchronously.
|
||||
*/
|
||||
private void doUpdateDisplayContents() {
|
||||
@@ -343,6 +352,7 @@ public class TextFieldAutocompleter<T> {
|
||||
|
||||
/**
|
||||
* 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<T> {
|
||||
/**
|
||||
* Check if the completion list window is visible.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Get the preferred dimensions of the completion list window.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Builds the list cell renderer for the autocompletion list.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Attach the autocompleter to the given text field.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Cause the currently-selected suggestion to be activated.
|
||||
*
|
||||
* <p>
|
||||
* By default, this is called when the user presses ENTER or clicks a suggestion.
|
||||
*/
|
||||
protected void activateCurrentCompletion() {
|
||||
@@ -516,6 +536,7 @@ public class TextFieldAutocompleter<T> {
|
||||
/**
|
||||
* Fire the registered autocompletion listeners on the given event.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
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<T> {
|
||||
|
||||
/**
|
||||
* Register the given auto-completion listener
|
||||
*
|
||||
* @param l the listener to register
|
||||
*/
|
||||
public void addAutocompletionListener(AutocompletionListener<T> l) {
|
||||
@@ -557,6 +580,7 @@ public class TextFieldAutocompleter<T> {
|
||||
|
||||
/**
|
||||
* Unregister the given auto-completion listener
|
||||
*
|
||||
* @param l the listener to unregister
|
||||
*/
|
||||
public void removeAutocompletionListener(AutocompletionListener<T> l) {
|
||||
@@ -565,6 +589,7 @@ public class TextFieldAutocompleter<T> {
|
||||
|
||||
/**
|
||||
* Get all the registered auto-completion listeners
|
||||
*
|
||||
* @return an array of registered listeners
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -576,7 +601,7 @@ public class TextFieldAutocompleter<T> {
|
||||
* 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> T[] getListeners(Class<T> listenerType) {
|
||||
@@ -660,12 +685,13 @@ public class TextFieldAutocompleter<T> {
|
||||
/**
|
||||
* Decide whether the given suggestion can be automatically activated.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* Starts the autocompleter on the given text field.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T> {
|
||||
/**
|
||||
* 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<T> {
|
||||
/**
|
||||
* 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<T> {
|
||||
/**
|
||||
* 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<T> 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<T> {
|
||||
}
|
||||
}
|
||||
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<T> {
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<T> {
|
||||
/**
|
||||
* A demonstration of the autocompleter on two linked text fields.
|
||||
*
|
||||
* <p>
|
||||
* This demo was designed to test whether the autocompleter and the {@link TextFieldLinker}
|
||||
* could be composed correctly.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
+11
-6
@@ -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.
|
||||
*/
|
||||
|
||||
+71
-7
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -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;
|
||||
}
|
||||
|
||||
+224
@@ -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));
|
||||
}
|
||||
}
|
||||
+44
@@ -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);
|
||||
}
|
||||
}
|
||||
-66
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user