GP-5196 Adding support for expressions and symbol names in address fields

This commit is contained in:
ghidragon 2024-12-31 16:00:39 -05:00
parent 14527b015e
commit b1d257150c
61 changed files with 2902 additions and 1445 deletions

View file

@ -398,6 +398,7 @@ src/main/help/help/topics/MemoryMapPlugin/images/MemoryMap.png||GHIDRA||||END|
src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png||GHIDRA||||END|
src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png||GHIDRA||||END|
src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png||GHIDRA||||END|
src/main/help/help/topics/Misc/AddressExpressions.htm||GHIDRA||||END|
src/main/help/help/topics/Misc/Appendix.htm||GHIDRA||||END|
src/main/help/help/topics/Misc/Welcome_to_Ghidra_Help.htm||GHIDRA||||END|
src/main/help/help/topics/Navigation/Navigation.htm||GHIDRA||||END|

View file

@ -196,6 +196,8 @@
<!-- Formerly The Byte Viewer -->
<tocdef id="Program Annotation" text="Program Annotation" target="help/topics/ProgramManagerPlugin/Program_Annotation.htm">
<tocdef id="Address Expressions" sortgroup="a" text="Address Expressions" target="help/topics/Misc/AddressExpressions.htm" />
<tocdef id="Auto Analysis" sortgroup="a" text="Auto Analysis" target="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm" >
<tocdef id="Forcing Analysis" sortgroup="a" text="Forcing Analysis" target="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Auto_Analyze" />
<tocdef id="Auto Analysis Options Panel" sortgroup="b" text="Auto Analysis Options Panel" target="help/topics/AutoAnalysisPlugin/AutoAnalysis.htm#Auto_Analysis_Option" />

View file

@ -55,8 +55,9 @@
<LI>Select the <B>User</B> radio button.</LI>
<LI>Enter an address, or click in the Code Browser at the address of the new
fallthrough.</LI>
<LI>Enter an address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>), or click in
the Code Browser at the address of the new fallthrough.</LI>
<LI>Select the <B>Apply</B> button to change the fallthrough and leave the dialog intact;
select the OK button to change the fallthrough and dismiss the dialog.</LI>

View file

@ -243,8 +243,10 @@
<P><B><I>Block Name</I></B> - Enter the name of the new memory block.</P>
<P><B><I>Start Addr</I></B> - Enter the start address of the new memory block.&nbsp; If the
program language defines multiple address spaces, the address space must also be
<P><B><I>Start Address</I></B> - Enter the start address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
of the new memory block.&nbsp; If
the program language defines multiple address spaces, the address space must also be
specified.&nbsp; The address space selection will not appear if only one is defined.
If creating an overlay memory block within an existing overlay address space that
space should be selected. A block within the default address space may not span across
@ -378,11 +380,14 @@
<P><B><I>Length -</I></B> Length of the memory block to be moved (not editable).</P>
<P><B><I>New Start Address -</I></B> Enter the NEW starting address for the block.&nbsp;
<P><B><I>New Start Address -</I></B> Enter the new starting address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
for the block.&nbsp;
The NEW ending address will be computed.</P>
<P><B><I>New End Address -&nbsp;</I></B> Enter the NEW ending address for the block. The
NEW starting address will be computed.</P>
<P><B><I>New End Address -&nbsp;</I></B> Enter the new ending address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
for the block. The NEW starting address will be computed.</P>
<P><I><IMG src="help/shared/note.png" border="0"> You cannot move a block under the
following conditions:</I></P>
@ -404,11 +409,15 @@
enter the split point:</P>
<UL>
<LI>Enter an end address for the first block (block to split), or</LI>
<LI>Enter an end address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
for the first block (block to split), or</LI>
<LI>Enter a length for the first block (block to split), or</LI>
<LI>Enter a start address for the second block (new block), or</LI>
<LI>Enter a start address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
for the second block (new block), or</LI>
<LI>Enter a length for the second block (new block).</LI>
</UL>
@ -482,7 +491,9 @@
<BLOCKQUOTE>
<P><B><I>New Start Address -</I></B> A new start address can be entered here.&nbsp; It must
<P><B><I>New Start Address -</I></B> A new start address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
can be entered here.&nbsp; It must
be before the current start address.</P>
<P><I><B>End Address -</B></I> Displays the end address of the block (not editable).</P>
@ -521,7 +532,9 @@
<P><I><B>Start Address -</B></I> Displays the start address of the block (not
editable).</P>
<P><B><I>New End Address -</I></B> A new end address can be entered here. It must be after
<P><B><I>New End Address -</I></B> A new end address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
can be entered here. It must be after
the current end address.</P>
<P><I><B>Block Length -</B></I> Displays the length of the block.&nbsp; A new value can be

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -0,0 +1,175 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Address Expressions</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY>
<H1><A name="Address_Expressions"></A>Address Expressions<BR>
</H1>
<BLOCKQUOTE>
<P>An address expression is an arithmetic expression that is entered into an address input
field and can include symbol names or memory block names that when evaluated results in a
program address.</P>
<H2>Operands</H2>
<BLOCKQUOTE>
<P>Operands can be either a number or a name that evaluates to an address.</P>
<H3>Names</H3>
<BLOCKQUOTE>
<P>If the operand is a name, then an attempt will be made to find a unique label or
function name in the current program for that name. If that fails, then memory blocks
will be searched looking for a memory block with that name. In either case, the associated
address for that label, function, or memory block will be used in evaluating the
expression.</P>
<P>Generally, symbols (addresses) must be the left operand for a binary operator and will
generate a address as the result (maintaining the address space). The one exception is
you can subtract two addresses and the result is a number.</P>
</BLOCKQUOTE>
<H3>Numbers</H3>
<BLOCKQUOTE>
<P>Numeric operands will be evaluated as either a hex number or decimal number, depending
on the <B>Hex/Decimal Mode</B> of the input field.</P>
<P>When in hex mode, undecorated numbers will be interpreted as if they are hex values.
(so "100" would evaluate to the number 256). If in decimal mode, hex numbers can still be
entered by prefixing them with "0x".</P>
<P>For fields that support either mode, the current mode will be displayed as "hint text"
in the lower right corner of the field. The mode can be toggled between decimal and hex
by pressing &lt;CTRL&gt; M.</P>
<P>When in hex mode, there is no corresponding prefix to use to specify a number as being
decimal. So if you want to have a mixed mode expression, use decimal mode and use the
"0x" prefix for any hex numbers in the expression.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Operators</H2>
<BLOCKQUOTE>
<P>Most standard operators are supported, but not all operators are supported for all
operands. Also order of operands is important when mixing numbers and addresses. For
example,a number can be added to an address, but an address can't be added to a number.</P>
<P>Operator precedence is the standard precedence defined by the "C" programming
language.</P>
<H3>Math Operators</H3>
<BLOCKQUOTE>
<P>Supported math operators are "+ - * /". These operators generate either a number or
address result depending on the operands.</P>
</BLOCKQUOTE>
<H3>Logical and Relational Operators</H3>
<BLOCKQUOTE>
<P>Supported logical operators are "&lt; &gt; &lt;= &gt;= == != || &amp;&amp; and !".
These operators generator a numeric value of 0 or 1.</P>
</BLOCKQUOTE>
<H3>Bit Operators</H3>
<BLOCKQUOTE>
<P>Supported bit operators are &lt;&lt;, &gt;&gt;, &amp;, |, ^, and ~. These operators
generate either a number or address result depending on the operands.</P>
</BLOCKQUOTE>
<H3>Groups Operators</H3>
<BLOCKQUOTE>
<P>Parenthesis can be used to group sub-expressions to control the order of
operations</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Result</H2>
<BLOCKQUOTE>
<P>The result of the expression is always an address or a number. If the result is a
number, it is converted to an address using the selected address space (in the case where
there are multiple possible address spaces, a combo is shown for choosing the desired
address space, otherwise the default address space is used)</P>
</BLOCKQUOTE>
<H2>Examples</H2>
<BLOCKQUOTE>
<TABLE width=95% border="1">
<TR>
<TH valign="top" bgcolor="#c0c0c0">Expression<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0">Result<BR>
</TH>
</TR>
<TR>
<TD valign="top"><B>ENTRY+10</B></TD>
<TD valign="top">Address 0x10 higher than the symbol "ENTRY"</TD>
</TR>
<TR>
<TD valign="top"><B>10+ENTRY</B></TD>
<TD valign="top">Error (Can't add an address to a number)</TD>
</TR>
<TR>
<TD valign="top"><B>100000+30</B></TD>
<TD valign="top">Address 0x100030 (hex mode)</TD>
</TR>
<TR>
<TD valign="top"><B>0x100000+30</B></TD>
<TD valign="top">Address 0x100030 (hex mode)</TD>
</TR>
<TR>
<TD valign="top"><B>0x100000+30</B></TD>
<TD valign="top">Address 0x10001e (decimal mode)</TD>
</TR>
<TR>
<TD valign="top"><B>0x100000+(2*10)</B></TD>
<TD valign="top">Address 0x100020</TD>
</TR>
<TR>
<TD valign="top"><B>ENTRY + 1&lt;&lt;4</B></TD>
<TD valign="top">Address that is 16 higher than the symbol "ENTRY"</TD>
</TR>
<TR>
<TD valign="top"><B>X - (X > 100) * 100</B></TD>
<TD valign="top">If symbol "X" address > 100, Result is 100 less than X; Otherwise X</TD>
</TR>
<TR>
<TD valign="top"><B>ENTRY | FF</B></TD>
<TD valign="top">Address that is the symbol "ENTRY" with its last 8 bits set to FF</TD>
</TR>
</TABLE>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BODY>
</HTML>

View file

@ -49,7 +49,9 @@
</LI>
<LI>The "Enter Base Address" field in the dialog is filled in with the first address in the
selection. You can enter a different address as the base address.</LI>
selection. You can enter a different address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>)
as the base address.</LI>
<LI>
The "Select Data Size" combo box has an entry for sizes 1, 2, 4, and 8. The size you select

View file

@ -1167,9 +1167,10 @@ c<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<P>[<I><B><IMG src="images/unchecked.png" alt="" valign="middle" align="middle">
Offset</B></I>] The <I>To Address</I> entry is required for normal memory references
and specifies the reference destination as a memory offset within a selected address
space. &nbsp;The address offset entry is always
interpretted as a unsigned hex value &nbsp;(i.e., the "0x" entry prefix is assumed).
&nbsp;For those processors with multiple address-spaces, a pull-down is also provided
space. Enter an address
(or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>) to specify
the referenced address.
For those processors with multiple address-spaces, a pull-down is also provided
allowing the address-space to be selected. Address spaces which overlay the
</I>OTHER</I> non-loaded space are only included if the <B>Include OTHER overlay spaces</B>
checkbox is selected.<BR>

View file

@ -124,7 +124,10 @@
range is smaller, then value associations for address range that was trucated is
effectively cleared. For example, in the dialog shown above, if you change the end
address to 01001b47 and change the value to 111, then addresses 01001b33 to 01001b47
will have the value 111 and address 01001b48 will have no value.<BR>
will have the value 111 and address 01001b48 will have no value. When entering a
new start or end address, you can also enter an
<A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>.
<BR>
</P>
</DIV>
</DIV>

View file

@ -117,15 +117,14 @@ class FallThroughDialog extends DialogComponentProvider implements ChangeListene
}
}
private void addressChanged() {
private void addressChanged(Address address) {
if (changing) {
return;
}
Runnable r = () -> {
Address addr = addrField.getAddress();
if (addr != null || addrField.getValue().length() == 0) {
model.setCurrentFallthrough(addr);
if (address != null || addrField.getText().length() == 0) {
model.setCurrentFallthrough(address);
}
else {
setStatusText("Invalid Address");
@ -139,9 +138,7 @@ class FallThroughDialog extends DialogComponentProvider implements ChangeListene
private JPanel create() {
JPanel panel = new JPanel(new BorderLayout(0, 10));
panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
addrField = new AddressInput();
addrField.setAddressFactory(model.getProgram().getAddressFactory());
addrField.addChangeListener(e -> addressChanged());
addrField = new AddressInput(model.getProgram(), this::addressChanged);
addrField.addActionListener(e -> model.setCurrentFallthrough(addrField.getAddress()));
panel.add(createHomePanel(), BorderLayout.NORTH);
panel.add(createAddressPanel(), BorderLayout.CENTER);

View file

@ -148,8 +148,9 @@ class VarnodeLocationCellEditor extends AbstractCellEditor
}
private Component createAddressEditor(VarnodeInfo varnode) {
addressInput = new AddressInput(BorderFactory.createEmptyBorder());
addressInput.setAddressFactory(program.getAddressFactory());
addressInput = new AddressInput(program);
addressInput.setComponentBorders(BorderFactory.createEmptyBorder());
Address address = varnode.getAddress();
if (address != null) {
addressInput.setAddress(address);

View file

@ -35,7 +35,6 @@ import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlockType;
@ -69,7 +68,6 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
private JCheckBox overlayCB;
private RegisterField initialValueField;
private JLabel initialValueLabel;
private AddressFactory addrFactory;
private AddressInput baseAddrField; // used for Bit and Byte mapped blocks
private IntegerTextField schemeDestByteCountField; // used for Byte mapped blocks
private IntegerTextField schemeSrcByteCountField; // used for Byte mapped blocks
@ -134,12 +132,12 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
}
private Component buildBasicInfoPanel() {
JPanel panel = new JPanel(new PairLayout(4, 10, 150));
JPanel panel = new JPanel(new PairLayout(5, 10, 150));
panel.setBorder(BorderFactory.createEmptyBorder(5, 7, 4, 5));
panel.add(new GLabel("Block Name:", SwingConstants.RIGHT));
panel.add(buildNameField());
panel.add(new GLabel("Start Addr:", SwingConstants.RIGHT));
panel.add(new GLabel("Start Address:", SwingConstants.RIGHT));
panel.add(buildAddressField());
panel.add(new GLabel("Length:", SwingConstants.RIGHT));
panel.add(buildLengthField());
@ -422,26 +420,16 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
model.setFileBytes((FileBytes) fileBytesComboBox.getSelectedItem());
}
private void addrChanged() {
Address addr = null;
try {
addr = addrField.getAddress();
}
catch (IllegalArgumentException e) {
// just let it be null
}
model.setStartAddress(addr);
private void addressChanged(Address address) {
model.setStartAddress(address);
}
private void baseAddressChanged() {
Address addr = null;
try {
addr = baseAddrField.getAddress();
}
catch (IllegalArgumentException e) {
// just let it be null
}
model.setBaseAddress(addr);
private void addressError(String errorMessage) {
model.setAddressError(errorMessage);
}
private void baseAddressChanged(Address address) {
model.setBaseAddress(address);
}
private void schemeSrcByteCountChanged() {
@ -476,12 +464,11 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
}
private JPanel buildMappedPanel() {
Program program = model.getProgram();
JPanel panel = new JPanel(new PairLayout());
baseAddrField = new AddressInput();
baseAddrField.setAddressFactory(addrFactory);
baseAddrField = new AddressInput(program, this::baseAddressChanged);
baseAddrField.setName("Source Addr");
baseAddrField.addChangeListener(ev -> baseAddressChanged());
baseAddrField.setAccessibleName("Source Address");
JPanel schemePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
@ -504,7 +491,6 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
schemePanel.add(new GLabel(" : "));
schemePanel.add(schemeSrcByteCountField.getComponent());
Program program = model.getProgram();
Address minAddr = program.getMinAddress();
if (minAddr == null) {
minAddr = program.getAddressFactory().getDefaultAddressSpace().getAddress(0);
@ -561,12 +547,12 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
}
private Component buildAddressField() {
addrField = new AddressInput();
Program program = model.getProgram();
addrField = new AddressInput(program, this::addressChanged);
addrField.setAddressErrorConsumer(this::addressError);
addrField.setAddressSpaceFilter(AddressInput.ALL_MEMORY_SPACES);
addrField.setName("Start Addr");
addrField.setAccessibleName("Memory Block Start Address");
addrFactory = model.getProgram().getAddressFactory();
addrField.setAddressFactory(addrFactory, AddressInput.INCLUDE_ALL_MEMORY_SPACES);
addrField.addChangeListener(ev -> addrChanged());
return addrField;
}

View file

@ -59,6 +59,7 @@ class AddBlockModel {
private String comment;
private FileBytes fileBytes;
private long fileBytesOffset = -1;
private String addressErrorMessage;
enum InitializedType {
UNINITIALIZED, INITIALIZED_FROM_VALUE, INITIALIZED_FROM_FILE_BYTES;
@ -90,6 +91,14 @@ class AddBlockModel {
void setStartAddress(Address addr) {
startAddr = addr;
addressErrorMessage = null;
validateInfo();
listener.stateChanged(null);
}
void setAddressError(String errorMessage) {
startAddr = null;
addressErrorMessage = errorMessage;
validateInfo();
listener.stateChanged(null);
}
@ -432,6 +441,9 @@ class AddBlockModel {
return true;
}
message = "Please enter a valid Start Address";
if (addressErrorMessage != null) {
message = "Invalid Address: " + addressErrorMessage;
}
return false;
}

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.memory;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.ActionListener;
import java.util.function.Consumer;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
@ -30,7 +31,7 @@ import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout;
@ -49,7 +50,6 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
private final static String EXPAND_UP_TITLE = "Expand Block Up";
private final static String EXPAND_DOWN_TITLE = "Expand Block Down";
private int dialogType;
private AddressFactory addrFactory;
private AddressInput startAddressInput;
private AddressInput endAddressInput;
private JTextField startField;
@ -66,7 +66,7 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
* @param af
* @param dialogType
*/
ExpandBlockDialog(PluginTool tool, ExpandBlockModel model, MemoryBlock block, AddressFactory af,
ExpandBlockDialog(PluginTool tool, ExpandBlockModel model, MemoryBlock block, Program program,
int dialogType) {
super(dialogType == EXPAND_UP ? EXPAND_UP_TITLE : EXPAND_DOWN_TITLE, true);
this.tool = tool;
@ -74,9 +74,8 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
this.dialogType = dialogType;
setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP,
dialogType == EXPAND_UP ? EXPAND_UP_TITLE : EXPAND_DOWN_TITLE));
addrFactory = af;
model.setChangeListener(this);
addWorkPanel(create(block));
addWorkPanel(create(block, program));
addOKButton();
addCancelButton();
setOkEnabled(false);
@ -109,16 +108,16 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
* Create the main work panel.
* @return JPanel
*/
private JPanel create(MemoryBlock block) {
private JPanel create(MemoryBlock block, Program program) {
JPanel panel = new JPanel(new PairLayout(5, 5, 150));
startAddressInput = new AddressInput();
AddressChangeListener listener = new AddressChangeListener();
startAddressInput = new AddressInput(program, listener);
startAddressInput.setName("NewStartAddress");
startAddressInput.setAddressFactory(addrFactory);
startAddressInput.setAccessibleName("New Start Address");
endAddressInput = new AddressInput();
endAddressInput = new AddressInput(program, listener);
endAddressInput.setName("EndAddress");
endAddressInput.setAddressFactory(addrFactory);
endAddressInput.setAccessibleName("New End Address");
Address start = block.getStart();
@ -161,9 +160,6 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
}
private void addListeners() {
startAddressInput.addChangeListener(new AddressChangeListener());
endAddressInput.addChangeListener(new AddressChangeListener());
lengthField.setChangeListener(new LengthChangeListener());
ActionListener al = e -> setStatusText("");
@ -206,10 +202,10 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene
* Listener on the AddressInput field; update length field when the
* address input field changes.
*/
private class AddressChangeListener implements ChangeListener {
private class AddressChangeListener implements Consumer<Address> {
@Override
public void stateChanged(ChangeEvent event) {
public void accept(Address address) {
if (isChanging) {
return;
}

View file

@ -690,7 +690,7 @@ class MemoryMapProvider extends ComponentProviderAdapter {
"OTHER overlay blocks can not be split.");
}
else {
SplitBlockDialog d = new SplitBlockDialog(plugin, block, program.getAddressFactory());
SplitBlockDialog d = new SplitBlockDialog(plugin, block, program);
tool.showDialog(d, this);
}
}
@ -711,7 +711,7 @@ class MemoryMapProvider extends ComponentProviderAdapter {
}
ExpandBlockDialog dialog =
new ExpandBlockDialog(tool, model, block, program.getAddressFactory(), dialogType);
new ExpandBlockDialog(tool, model, block, program, dialogType);
model.initialize(block);
dialog.dispose();
}

View file

@ -28,7 +28,7 @@ import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import ghidra.util.layout.PairLayout;
@ -82,9 +82,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
setOkEnabled(false);
changing = true;
if (!isVisible()) {
AddressFactory factory = model.getAddressFactory();
newStartField.setAddressFactory(factory);
newEndField.setAddressFactory(factory);
Program program = model.getProgram();
newStartField.setProgram(program);
newEndField.setProgram(program);
}
Address newStart = model.getNewStartAddress();
if (newStart != null) {
@ -136,8 +136,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
}
private JPanel buildMainPanel() {
JPanel panel = new JPanel(new PairLayout(5, 20, 150));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JPanel panel = new JPanel(new PairLayout(2, 10, 150));
panel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
blockNameLabel = new GDLabel(".text");
blockNameLabel.setName("blockName"); // name components for junits
@ -150,15 +150,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
lengthLabel = new GDLabel("4096 (0x1000)");
lengthLabel.setName("length");
newStartField = new AddressInput();
newStartField = new AddressInput(model.getProgram(), this::startChanged);
newStartField.setName("newStart");
newEndField = new AddressInput();
newEndField = new AddressInput(model.getProgram(), this::endChanged);
newEndField.setName("newEnd");
newStartField.addChangeListener(e -> startChanged());
newEndField.addChangeListener(e -> endChanged());
panel.add(new GLabel("Name:", SwingConstants.RIGHT));
panel.add(blockNameLabel);
panel.add(new GLabel("Start Address:", SwingConstants.RIGHT));
@ -174,13 +171,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
return panel;
}
private void startChanged() {
private void startChanged(Address address) {
if (changing) {
return;
}
Address newStart = newStartField.getAddress();
if (newStart != null) {
model.setNewStartAddress(newStart);
if (address != null) {
model.setNewStartAddress(address);
}
else {
setStatusText("Invalid Address");
@ -188,13 +184,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
}
}
private void endChanged() {
private void endChanged(Address address) {
if (changing) {
return;
}
Address newEnd = newEndField.getAddress();
if (newEnd != null) {
model.setNewEndAddress(newEnd);
if (address != null) {
model.setNewEndAddress(address);
}
else {
setStatusText("Invalid Address");

View file

@ -18,7 +18,8 @@ package ghidra.app.plugin.core.memory;
import ghidra.app.cmd.memory.MoveBlockListener;
import ghidra.app.cmd.memory.MoveBlockTask;
import ghidra.framework.model.*;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
@ -183,8 +184,8 @@ class MoveBlockModel implements DomainObjectListener {
program = null;
}
AddressFactory getAddressFactory() {
return program.getAddressFactory();
Program getProgram() {
return program;
}
private Address getEndAddress(Address start) {

View file

@ -27,7 +27,9 @@ import docking.widgets.label.GLabel;
import ghidra.app.plugin.core.misc.RegisterField;
import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.HelpLocation;
@ -50,16 +52,14 @@ class SplitBlockDialog extends DialogComponentProvider {
private JTextField blockTwoEndField;
private RegisterField blockTwoLengthField;
private MemoryBlock block;
private AddressFactory addrFactory;
private MemoryMapPlugin plugin;
SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, AddressFactory af) {
SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, Program program) {
super("Split Block");
this.plugin = plugin;
this.block = block;
addrFactory = af;
setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP, "Split Block"));
addWorkPanel(create());
addWorkPanel(create(program));
addOKButton();
addCancelButton();
setOkEnabled(false);
@ -89,8 +89,8 @@ class SplitBlockDialog extends DialogComponentProvider {
* Create the work panel.
* @return JPanel
*/
private JPanel create() {
JPanel panelOne = new JPanel(new PairLayout(5, 5, 150));
private JPanel create(Program program) {
JPanel panelOne = new JPanel(new PairLayout(5, 10, 150));
panelOne.setBorder(BorderFactory.createTitledBorder("Block to Split"));
blockOneNameField = new JTextField(10);
blockOneNameField.setName("BlockOneName");
@ -99,7 +99,7 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneStartField.setName("BlockOneStart");
blockOneStartField.getAccessibleContext().setAccessibleName("Address of Block To Split");
blockOneEnd = new AddressInput();
blockOneEnd = new AddressInput(program, this::blockOneEndChanged);
blockOneEnd.setName("BlockOneEnd");
blockOneEnd.setAccessibleName("New Block End Adddress");
@ -121,7 +121,7 @@ class SplitBlockDialog extends DialogComponentProvider {
blockTwoNameField = new JTextField(10);
blockTwoNameField.setName("BlockTwoName");
blockTwoNameField.getAccessibleContext().setAccessibleName("Name of New Block");
blockTwoStart = new AddressInput();
blockTwoStart = new AddressInput(program, this::blockTwoStartChanged);
blockTwoStart.setName("BlockTwoStart");
blockTwoStart.setAccessibleName("New Block Start Address");
blockTwoEndField = new JTextField(10);
@ -141,11 +141,11 @@ class SplitBlockDialog extends DialogComponentProvider {
panelTwo.add(blockTwoLengthField);
JPanel mainPanel = new JPanel();
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
BoxLayout bl = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(bl);
mainPanel.add(Box.createVerticalStrut(5));
mainPanel.add(panelOne);
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(Box.createVerticalStrut(20));
mainPanel.add(panelTwo);
return mainPanel;
@ -166,7 +166,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneStartField.setText(startAddr.toString());
blockOneStartField.setEnabled(false);
blockOneEnd.setAddressFactory(addrFactory);
blockOneEnd.setAddress(endAddr);
blockOneEnd.setAddressSpaceEditable(false);
@ -174,7 +173,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockTwoNameField.setText(name + ".split");
blockTwoStart.setAddressFactory(addrFactory);
blockTwoStart.setAddress(startAddr);
blockTwoStart.setAddressSpaceEditable(false);
@ -189,8 +187,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneLengthField.setChangeListener(new LengthChangeListener(blockOneLengthField));
blockTwoLengthField.setChangeListener(new LengthChangeListener(blockTwoLengthField));
blockOneEnd.addChangeListener(new AddressChangeListener(blockOneEnd));
blockTwoStart.addChangeListener(new AddressChangeListener(blockTwoStart));
ActionListener al = e -> setStatusText("");
blockOneLengthField.addActionListener(al);
@ -307,135 +303,101 @@ class SplitBlockDialog extends DialogComponentProvider {
}
}
/**
* Listener on the AddressInput fields; update other fields when either
* of these fields change.
*/
private class AddressChangeListener implements ChangeListener {
private void blockOneEndChanged(Address end) {
setStatusText("");
boolean isValid = checkBlockOneEndAddress(end);
setOkEnabled(isValid);
}
AddressInput source;
private void blockTwoStartChanged(Address start) {
setStatusText("");
boolean isValid = checkBlockTwoStartChanged(start);
setOkEnabled(isValid);
}
public AddressChangeListener(AddressInput source) {
this.source = source;
private boolean checkBlockOneEndAddress(Address end) {
if (end == null) {
setStatusText("Invalid Address");
return false;
}
@Override
public void stateChanged(ChangeEvent event) {
setStatusText("");
boolean ok = false;
if (source == blockOneEnd) {
ok = blockOneEndChanged();
}
else if (source == blockTwoStart) {
ok = blockTwoStartChanged();
}
setOkEnabled(ok);
Address start = block.getStart();
if (end.compareTo(start) < 0) {
setStatusText("End address must be greater than start");
return false;
}
private Address getAddress() throws InvalidInputException {
AddressInput field = source;
Address addr = field.getAddress();
if (addr == null && field.hasInput()) {
throw new InvalidInputException();
}
return addr;
if (end.compareTo(block.getEnd()) == 0) {
return false;
}
private boolean blockOneEndChanged() {
Address start = block.getStart();
Address end = null;
try {
end = getAddress();
}
catch (InvalidInputException e) {
setStatusText("Invalid Address");
return false;
}
if (end == null) {
return false;
}
if (end.compareTo(start) < 0) {
setStatusText("End address must be greater than start");
return false;
}
if (end.compareTo(block.getEnd()) == 0) {
return false;
}
// change block One length and blockTwoStart, blockTwoLength
long length = 0;
try {
length = end.subtract(start) + 1;
}
catch (IllegalArgumentException e) {
setStatusText(e.getMessage());
return false;
}
long blockSize = block.getSize();
if (length > blockSize) {
setStatusText(
"End address must be less than original block end (" + block.getEnd() + ")");
return false;
}
blockOneLengthField.setValue(Long.valueOf(length));
try {
Address b2Start = end.addNoWrap(1);
blockTwoStart.setAddress(b2Start);
length = block.getEnd().subtract(b2Start) + 1;
blockTwoLengthField.setValue(Long.valueOf(length));
}
catch (Exception e) {
if (e instanceof AddressOverflowException) {
setStatusText("Could not create new start address");
}
return false;
}
return true;
// change block One length and blockTwoStart, blockTwoLength
long length = 0;
try {
length = end.subtract(start) + 1;
}
catch (IllegalArgumentException e) {
setStatusText(e.getMessage());
return false;
}
long blockSize = block.getSize();
if (length > blockSize) {
setStatusText(
"End address must be less than original block end (" + block.getEnd() + ")");
return false;
}
blockOneLengthField.setValue(Long.valueOf(length));
private boolean blockTwoStartChanged() {
Address start = null;
try {
start = getAddress();
}
catch (InvalidInputException e) {
setStatusText("Invalid Address");
return false;
}
Address end = block.getEnd();
if (start == null) {
return false;
}
else if (start.compareTo(end) > 0) {
setStatusText("Start address must not be greater than end");
return false;
}
else if (start.compareTo(block.getStart()) <= 0) {
setStatusText("Start address must be greater than original block start (" +
block.getStart() + ")");
return false;
}
// change block Two length, blockOneEnd, block One length
long length = end.subtract(start) + 1;
try {
Address b2Start = end.addNoWrap(1);
blockTwoStart.setAddress(b2Start);
length = block.getEnd().subtract(b2Start) + 1;
blockTwoLengthField.setValue(Long.valueOf(length));
try {
Address b1End = start.subtractNoWrap(1);
blockOneEnd.setAddress(b1End);
length = b1End.subtract(block.getStart()) + 1;
blockOneLengthField.setValue(Long.valueOf(length));
}
catch (Exception e) {
if (e instanceof AddressOverflowException) {
setStatusText("Could not create end address for split block");
}
return false;
}
return true;
}
catch (Exception e) {
if (e instanceof AddressOverflowException) {
setStatusText("Could not create new start address");
}
return false;
}
return true;
}
private boolean checkBlockTwoStartChanged(Address start) {
if (start == null) {
setStatusText("Invalid Address");
return false;
}
Address end = block.getEnd();
if (start == null) {
return false;
}
else if (start.compareTo(end) > 0) {
setStatusText("Start address must not be greater than end");
return false;
}
else if (start.compareTo(block.getStart()) <= 0) {
setStatusText("Start address must be greater than original block start (" +
block.getStart() + ")");
return false;
}
// change block Two length, blockOneEnd, block One length
long length = end.subtract(start) + 1;
blockTwoLengthField.setValue(Long.valueOf(length));
try {
Address b1End = start.subtractNoWrap(1);
blockOneEnd.setAddress(b1End);
length = b1End.subtract(block.getStart()) + 1;
blockOneLengthField.setValue(Long.valueOf(length));
}
catch (Exception e) {
if (e instanceof AddressOverflowException) {
setStatusText("Could not create end address for split block");
}
return false;
}
return true;
}
}

View file

@ -231,7 +231,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
updateExtLibPath();
extLabel.setText(extLoc.getLabel());
extAddr.setAddressFactory(program.getAddressFactory());
extAddr.setProgram(program);
Address addr = extLoc.getAddress();
if (addr != null) {
extAddr.setAddress(addr);
@ -260,7 +260,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
extLibPath.setText(null);
extLabel.setText(null);
extAddr.setAddressFactory(program.getAddressFactory());
extAddr.setProgram(program);
extAddr.clear();
extLibName.requestFocus();

View file

@ -191,7 +191,8 @@ class EditMemoryReferencePanel extends EditReferencePanel {
}
private void initializeToAddressField(Address toAddr) {
toAddressField.setAddressFactory(fromCodeUnit.getProgram().getAddressFactory(), (s) -> {
Program program = fromCodeUnit.getProgram();
toAddressField.setProgram(program, (s) -> {
if (s.isLoadedMemorySpace()) {
return true;
}

View file

@ -19,8 +19,6 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.DialogComponentProvider;
import docking.widgets.checkbox.GCheckBox;
@ -30,7 +28,7 @@ import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.layout.PairLayout;
@ -44,7 +42,6 @@ import ghidra.util.layout.PairLayout;
public class OffsetTableDialog extends DialogComponentProvider {
private AddressInput addrInput;
private AddressFactory addrFactory;
private JComboBox<String> comboBox;
private Address defaultAddress;
private JCheckBox signedCheckBox;
@ -52,15 +49,13 @@ public class OffsetTableDialog extends DialogComponentProvider {
/**
* Construct a new dialog
* @param parent parent of this dialog
* @param defaultAddress address to put in the address field as a default
* @param addrFactory address factory required by AddressInput object
* @param program the program
*/
OffsetTableDialog(Address defaultAddress, AddressFactory addrFactory) {
OffsetTableDialog(Address defaultAddress, Program program) {
super("Create Offset References", true);
this.defaultAddress = defaultAddress;
this.addrFactory = addrFactory;
addWorkPanel(buildMainPanel());
addWorkPanel(buildMainPanel(program));
addOKButton();
addCancelButton();
setHelpLocation(new HelpLocation(HelpTopics.REFERENCES, "Create_Offset_References"));
@ -128,23 +123,16 @@ public class OffsetTableDialog extends DialogComponentProvider {
signedCheckBox.setSelected(isSigned);
}
private JPanel buildMainPanel() {
private JPanel buildMainPanel(Program program) {
JPanel panel = new JPanel(new PairLayout(10, 5));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20));
addrInput = new AddressInput();
addrInput = new AddressInput(program, a -> clearStatusText());
addrInput.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
okCallback();
}
});
addrInput.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
clearStatusText();
}
});
addrInput.setAddressFactory(addrFactory);
addrInput.setAddress(defaultAddress);
panel.add(new GLabel("Enter Base Address:", SwingConstants.RIGHT));

View file

@ -101,10 +101,10 @@ public class OffsetTablePlugin extends Plugin {
true);
return;
}
AddressFactory addressFactory = context.getProgram().getAddressFactory();
Program program = context.getProgram();
Address minAddress = context.getSelection().getMinAddress();
OffsetTableDialog dialog = new OffsetTableDialog(minAddress, addressFactory);
OffsetTableDialog dialog = new OffsetTableDialog(minAddress, program);
dialog.setSelectedSize(lastSelectedSize);
dialog.setSigned(lastSigned);
try {

View file

@ -16,17 +16,18 @@
package ghidra.app.plugin.core.register;
import java.math.BigInteger;
import java.util.function.Consumer;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.DialogComponentProvider;
import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.app.util.bean.FixedBitSizeValueField;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.MessageType;
import ghidra.util.layout.PairLayout;
@ -39,9 +40,9 @@ class EditRegisterValueDialog extends DialogComponentProvider {
private boolean wasCancelled = true;
EditRegisterValueDialog(Register register, Address start, Address end, BigInteger value,
AddressFactory factory) {
Program program) {
super("Edit Register Value Range");
addWorkPanel(buildWorkPanel(register, start, end, value, factory));
addWorkPanel(buildWorkPanel(register, start, end, value, program));
addOKButton();
addCancelButton();
@ -49,24 +50,15 @@ class EditRegisterValueDialog extends DialogComponentProvider {
}
private JComponent buildWorkPanel(Register register, Address start, Address end,
BigInteger value, AddressFactory factory) {
BigInteger value, Program program) {
JTextField registerField =
new JTextField(register.getName() + " (" + register.getBitLength() + ")");
registerField.setEditable(false);
startAddrField = new AddressInput();
endAddrField = new AddressInput();
ChangeListener changeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
updateOk();
}
};
startAddrField.setAddressFactory(factory);
endAddrField.setAddressFactory(factory);
startAddrField.addChangeListener(changeListener);
endAddrField.addChangeListener(changeListener);
Consumer<Address> addressChangeListener = a -> updateOk();
startAddrField = new AddressInput(program, addressChangeListener);
endAddrField = new AddressInput(program, addressChangeListener);
registerValueField = new FixedBitSizeValueField(register.getBitLength(), true, false);
startAddrField.setAddress(start);

View file

@ -89,7 +89,7 @@ class RegisterValuesPanel extends JPanel {
Address end = range.getEndAddress();
BigInteger value = range.getValue();
EditRegisterValueDialog dialog = new EditRegisterValueDialog(selectedRegister, start, end,
value, currentProgram.getAddressFactory());
value, currentProgram);
tool.showDialog(dialog, this);
if (!dialog.wasCancelled()) {

View file

@ -300,7 +300,7 @@ class EditExternalLocationPanel extends JPanel {
if (extOriginalLabelTextField != null) {
extOriginalLabelTextField.setText(startingOriginalName);
}
extAddressInputWidget.setAddressFactory(program.getAddressFactory());
extAddressInputWidget.setProgram(program);
if (startingLocationAddress != null) {
extAddressInputWidget.setAddress(startingLocationAddress);
}

View file

@ -2630,7 +2630,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
Address choice = doAsk(Integer.class, title, message, existingValue, lastValue -> {
AskAddrDialog dialog =
new AskAddrDialog(title, message, currentProgram.getAddressFactory(), lastValue);
new AskAddrDialog(title, message, currentProgram, lastValue);
if (dialog.isCanceled()) {
throw new CancelledException();
}

View file

@ -16,34 +16,44 @@
package ghidra.app.util;
import java.awt.BorderLayout;
import java.awt.FontMetrics;
import java.awt.CardLayout;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import docking.widgets.combobox.GComboBox;
import docking.widgets.table.FocusableEditor;
import docking.widgets.textfield.HexDecimalModeTextField;
import generic.expressions.ExpressionException;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.AddressEvaluator;
import utility.function.Dummy;
/**
* Panel for user input of addresses. Handles case with multiple address
* spaces.
* Input field for entering address or address expression. Handles multiple address
* spaces and supports both hex and decimal number modes for evaluating numbers.
*/
public class AddressInput extends JPanel implements FocusableEditor {
private JTextField textField;
private JComboBox<AddressSpace> combo;
public final static Predicate<AddressSpace> ALL_MEMORY_SPACES = s -> s.isMemorySpace();
public final static Predicate<AddressSpace> LOADED_MEMORY_SPACES = s -> s.isLoadedMemorySpace();
private HexDecimalModeTextField textField;
private AddressSpaceField addressSpaceField;
AddressEvaluator addressEvaluator;
private Predicate<AddressSpace> addressSpaceFilter = LOADED_MEMORY_SPACES;
private Consumer<Address> addressChangedConsumer;
private Consumer<String> addressErrorConsumer = Dummy.consumer();
private boolean comboAdded;
private AddressFactory addrFactory;
private ChangeListener changeListener;
private boolean updatingAddress;
private boolean updateSpaceField;
private boolean stateChanging;
private JTextField spaceField;
private boolean assumeHex = true;
private boolean notificationsEnabled = true;
private static final Comparator<AddressSpace> ADDRESS_SPACE_SORT_COMPARATOR = (s1, s2) -> {
if (s1.isOverlaySpace()) {
@ -58,65 +68,132 @@ public class AddressInput extends JPanel implements FocusableEditor {
};
/**
* Constructor for AddressInput.
* @param border border around each subcomponent (combo/text fields).
* Constructs an AddressInput field with no specified program or address.
*/
public AddressInput(Border border) {
this();
combo.setBorder(border);
public AddressInput() {
this(null, null, null);
}
/**
* Constructs an AddressInput field with a consumer to be called when the address field's
* value changes.
* @param addressChangedConsumer the consumer to be called when the value in the address field
* changes
*/
public AddressInput(Consumer<Address> addressChangedConsumer) {
this(null, null, addressChangedConsumer);
}
/**
* Constructs an AddressInput field and initialized with a program.
* @param program the program used to evaluate the entered address expression.
*/
public AddressInput(Program program) {
this(program, null, null);
}
/**
* Constructs an AddressInput field and initialized with an address factory.
* @param factory the address factory used to evaluate the entered address expression.
*/
public AddressInput(AddressFactory factory) {
this(null, factory, null);
}
/**
* Constructs an AddressInput field with a consumer to be notified when the address field
* changes and initialized with a program.
* @param program the program used to evaluate the entered address expression.
* @param addressChangedConsumer the consumer to be called when the value in the address field
* changes
*/
public AddressInput(Program program, Consumer<Address> addressChangedConsumer) {
this(program, null, addressChangedConsumer);
}
/**
* Constructs an AddressInput field with a consumer to be notified when the address field
* changes and initialized with an address factory.
* @param factory the address factory used to evaluate the entered address expression.
* @param addressChangedConsumer the consumer to be called when the value in the address field
* changes
*/
public AddressInput(AddressFactory factory, Consumer<Address> addressChangedConsumer) {
this(null, factory, addressChangedConsumer);
}
private AddressInput(Program program, AddressFactory factory,
Consumer<Address> addressChangedConsumer) {
this.addressChangedConsumer = Dummy.ifNull(addressChangedConsumer);
buildComponent();
if (program != null) {
setProgram(program);
}
else if (factory != null) {
setAddressFactory(factory);
}
}
/**
* Sets a filter predicate to determine which address spaces should be selectable by the user.
* If after filtering only one space is remaining, the address space portion of the address
* input field will not be shown.
* @param spaceFilter the predicate for filtering selectable address spaces.
*/
public void setAddressSpaceFilter(Predicate<AddressSpace> spaceFilter) {
this.addressSpaceFilter = spaceFilter;
updateAddressSpaceCombo();
}
/**
* Sets the text in the expression input textfield.
* @param text the text to initialize the input textfield
*/
public void setText(String text) {
textField.setText(text);
}
/**
* Used to set the internal borders for use in specialized use cases such as a table field
* editor.
* @param border the border to use for the internal components that make up this input field
*/
public void setComponentBorders(Border border) {
addressSpaceField.setComponentsBorder(border);
textField.setBorder(border);
}
/**
* Constructor for AddressInput.
* Sets the hex/decimal mode for this field. When in hex mode, all numbers are assumed to be
* hexadecimal values. When in decimal mode, all numbers are assumed to be decimal numbers
* unless prefixed with "0x".
* @param hexMode true to assume numbers are hexadecimal.
*/
public AddressInput() {
setLayout(new BorderLayout());
textField = new JTextField(10);
textField.setName("JTextField");//for JUnits...
combo = new GComboBox<>();
combo.setName("JComboBox");//for JUnits...
combo.getAccessibleContext().setAccessibleName("Address Space");
add(textField, BorderLayout.CENTER);
//add(combo, BorderLayout.WEST);
comboAdded = false;
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
stateChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
stateChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
stateChanged();
}
});
combo.addActionListener(ev -> stateChanged());
public void setAssumeHex(boolean hexMode) {
textField.setHexMode(hexMode);
hexModeChanged(hexMode);
}
/**
* Set the field to display the given address
* @param address the new address to display
*/
public void setAddress(Address addr) {
if (stateChanging) {
// called while we are in doing a state changed notification
public void setAddress(Address address) {
if (address.equals(getAddress())) {
return;
}
updatingAddress = true;
textField.setText(addr.toString(false));
combo.setSelectedItem(addr.getAddressSpace());
updatingAddress = false;
if (updateSpaceField) {
updateSpaceField = false;
spaceField.setText(addr.getAddressSpace().getName());
notificationsEnabled = false;
try {
String addressString = address.toString(false);
addressString = removeLeadingZeros(addressString);
if (!assumeHex) {
addressString = "0x" + addressString;
}
textField.setText(addressString);
addressSpaceField.setAddressSpace(address.getAddressSpace());
}
finally {
notificationsEnabled = true;
}
}
@ -124,42 +201,53 @@ public class AddressInput extends JPanel implements FocusableEditor {
* Returns the address in the field or null if the address can't
* be parsed.
* @return The address for the current value in the text field
* @throws ExpressionException if expression can not be evaluated to a valid address.
*
* @throws NullPointerException if AddressFactory has not been set.
*/
public Address getAddress() {
String addrStr = textField.getText();
AddressSpace space = getAddressSpace();
try {
return space.getAddress(addrStr);
public Address getAddressWithExceptions() throws ExpressionException {
String addrExpression = textField.getText();
if (addrExpression.isBlank()) {
return null;
}
catch (AddressFormatException e) {
return addressEvaluator.parseAsAddress(addrExpression);
}
/**
* Gets the current address the field evaluates to or null if the text does not evaluate to
* a valid, unique address.
* @return the current address the field evalutes to or null if the text does not evalute to
* a valid unique address.
*/
public Address getAddress() {
try {
return getAddressWithExceptions();
}
catch (ExpressionException e) {
return null;
}
}
/**
* Returns the address space selected in the combobox or in the input text itself
* if specified (eg: "register:1"). If the address space is not specified; returns the
* default space.
* Returns the address space selected in the combobox the default address space if the
* comboBox is not being shown.
*
* @throws NullPointerException if AddressFactory has not been set.
* @return the selected address space, or the default address space if no combo added, or
* null if no program is set.
*/
public AddressSpace getAddressSpace() {
if (comboAdded) {
return (AddressSpace) combo.getSelectedItem();
}
return addrFactory.getDefaultAddressSpace();
return addressSpaceField.getAddressSpace();
}
/**
* Returns true if the Address input field contains text.
* The getAddress() method will return null if text is not
* a valid address.
* @return true if the address field is not blank
*/
public boolean hasInput() {
return textField.getText().length() != 0;
return !textField.getText().isBlank();
}
/**
@ -167,96 +255,48 @@ public class AddressInput extends JPanel implements FocusableEditor {
* @return the text in this field
*/
public String getText() {
return textField.getText();
}
public AddressFactory getAddressFactory() {
return addrFactory;
return textField.getText().trim();
}
/**
* Address Space predicate which includes all loaded memory spaces.
* See {@link AddressSpace#isLoadedMemorySpace()}.
* Intended for use with {@link #setAddressFactory(AddressFactory, Predicate)}.
*/
public final static Predicate<AddressSpace> INCLUDE_LOADED_MEMORY_SPACES = (s) -> {
return s.isLoadedMemorySpace();
};
/**
* Address Space predicate which includes all memory spaces, including the
* {@link AddressSpace#OTHER_SPACE} and all overlay spaces.
* Intended for use with {@link #setAddressFactory(AddressFactory, Predicate)}.
*/
public final static Predicate<AddressSpace> INCLUDE_ALL_MEMORY_SPACES = (s) -> {
return s.isMemorySpace();
};
/**
* Set the address factory to be used to parse addresses. Also
* used to set the combo box with the list of valid address spaces
* if there is more than one space. Only loaded memory spaces
* Set the program to be used to parse addresses and expressions and also
* to determine the list of valid address spaces. Only loaded memory spaces
* will be allowed (see {@link AddressSpace#isLoadedMemorySpace()}).
* @param factory address factory to use
* @param program the program to use to resolve address expressions
*/
public void setProgram(Program program) {
addressEvaluator = new AddressEvaluator(program, assumeHex);
updateAddressSpaceCombo();
}
/**
* Sets the program and the address space filter at the same time. This avoid some weird
* intermediate results if the are set separately.
* @param program the program to use to parse addresses and expressions.
* @param addessSpaceFilter the predicate to determine which address spaces are user selectable
*/
public void setProgram(Program program, Predicate<AddressSpace> addessSpaceFilter) {
this.addressSpaceFilter = addessSpaceFilter;
setProgram(program);
}
/**
* Legacy method for setting the address factory to be used to parse address. Should only be
* used when a program is not readily available.
* @param factory the address factory to be used to parse addresses.
*/
public void setAddressFactory(AddressFactory factory) {
setAddressFactory(factory, INCLUDE_LOADED_MEMORY_SPACES);
addressEvaluator = new AddressEvaluator(factory, assumeHex);
updateAddressSpaceCombo();
}
/**
* Set the address factory to be used to parse addresses. Also used to set the combo box
* with the list of valid address spaces if there is more than one space. The specified
* predicate will be used to determine if an address space should be included.
* @param factory address factory to use
* @param predicate callback used to determine if an address space should be included for selection
* Sets a consumer to be notified when the address input field changes, but can't be parsed
* into a valid address.
* @param addressErrorConsumer the consumer to be notified for bad address input
*/
public void setAddressFactory(AddressFactory factory, Predicate<AddressSpace> predicate) {
this.addrFactory = factory;
AddressSpace[] spaces = factory.getAddressSpaces();
Arrays.sort(spaces, ADDRESS_SPACE_SORT_COMPARATOR);
DefaultComboBoxModel<AddressSpace> model = new DefaultComboBoxModel<>();
combo.setModel(model);
FontMetrics fm = combo.getFontMetrics(combo.getFont());
int width = 0;
for (AddressSpace space : spaces) {
if (!predicate.test(space)) {
continue;
}
String s = space.toString();
width = Math.max(width, fm.stringWidth(s));
model.addElement(space);
}
// // Commented out the following 2 lines since they were causing the Combo to only
// // display "..." in some cases instead of the actual address space name.
// Dimension d = combo.getPreferredSize();
// combo.setPreferredSize(new Dimension(width + 30, d.height));
if (model.getSize() > 1) {
if (!comboAdded) {
add(combo, BorderLayout.WEST);
comboAdded = true;
}
}
else if (comboAdded) {
remove(combo);
comboAdded = false;
}
invalidate();
}
/**
* Sets the selected combo box item
* to the default address space.
*/
public void selectDefaultAddressSpace() {
if (addrFactory != null) {
AddressSpace space = addrFactory.getDefaultAddressSpace();
combo.setSelectedItem(space);
}
public void setAddressErrorConsumer(Consumer<String> addressErrorConsumer) {
this.addressErrorConsumer = addressErrorConsumer;
}
/**
@ -273,32 +313,15 @@ public class AddressInput extends JPanel implements FocusableEditor {
textField.selectAll();
}
/**
* Get the offset part of the address field.
* @return String
*/
public String getValue() {
return textField.getText();
}
/**
* Set the offset part of the address offset field without changing address space.
* NOTE: This method is intended for test use only and mimicks user input.
* @param value the offset value string
*/
public void setValue(String value) {
textField.setText(value);
}
/**
* Set the address space and offset.
* NOTE: Unlike {@link #setAddress(Address)} this method is intended for test use only
* and mimicks user input with {@link #stateChanged()} notification.
* and mimics user input with address changed notification
* @param addr the address value
*/
public void setValue(Address addr) {
public void simulateAddressChanged(Address addr) {
setAddress(addr);
stateChanged();
notifyAddressChanged();
}
@Override
@ -318,27 +341,7 @@ public class AddressInput extends JPanel implements FocusableEditor {
* @param state false means that the combo box should not be editable
*/
public void setAddressSpaceEditable(boolean state) {
if (!state && comboAdded) {
AddressSpace selectedSpace = (AddressSpace) combo.getSelectedItem();
String spaceName = selectedSpace != null ? selectedSpace.getName() + ":" : " ";
spaceField = new JTextField(spaceName);
spaceField.setEnabled(false);
remove(combo);
add(spaceField, BorderLayout.WEST);
if (textField.getText().length() == 0) {
updateSpaceField = true;
}
}
}
/**
* Adds a change listener that will be notified anytime this address value
* in this panel changes
* @param listener the change listener to be notified.
*/
public void addChangeListener(ChangeListener listener) {
changeListener = listener;
addressSpaceField.setEditable(state);
}
/**
@ -352,20 +355,17 @@ public class AddressInput extends JPanel implements FocusableEditor {
/**
* Removes the action listener from the list to be notified.
* @param listener
* @param listener the listener to be removed
*/
public void removeActionListener(ActionListener listener) {
this.textField.removeActionListener(listener);
}
/**
* @see java.awt.Component#setEnabled(boolean)
*/
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
textField.setEnabled(enabled);
combo.setEnabled(enabled);
addressSpaceField.setEnabled(enabled);
}
/**
@ -377,59 +377,204 @@ public class AddressInput extends JPanel implements FocusableEditor {
}
/**
* Set the text field to be editable according to the state param.
* Set the text field to be editable or not.
* @param b true if the address input field can be edited
*/
public void setEditable(boolean state) {
textField.setEditable(state);
public void setEditable(boolean b) {
textField.setEditable(b);
addressSpaceField.setEditable(b);
}
/**
* Returns true if the address input field is editable.
* @return true if the address input field is editable.
*/
public boolean isEditable() {
return textField.isEditable();
}
@Override
public void focusEditor() {
if (comboAdded) {
combo.requestFocusInWindow();
if (addressSpaceField.getSpaceCount() > 1 && addressSpaceField.isEnabled()) {
addressSpaceField.requestFocusInWindow();
}
else {
textField.requestFocusInWindow();
}
}
private void stateChanged() {
if (changeListener != null && !updatingAddress && !stateChanging) {
stateChanging = true;
changeListener.stateChanged(null);
stateChanging = false;
}
}
public void showAddressSpaceCombo(boolean showCombo) {
if (showCombo) {
if (!comboAdded) {
add(combo, BorderLayout.WEST);
comboAdded = true;
}
}
else if (comboAdded) {
remove(combo);
comboAdded = false;
}
revalidate();
}
@Override
public void requestFocus() {
textField.requestFocus();
}
protected JTextField getAddressTextField() {
return textField;
private void buildComponent() {
setLayout(new BorderLayout());
textField = new HexDecimalModeTextField(10, b -> hexModeChanged(b));
textField.setHexMode(true);
textField.setName("JTextField");//for JUnits...
addressSpaceField = new AddressSpaceField();
add(textField, BorderLayout.CENTER);
comboAdded = false;
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
notifyAddressChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
notifyAddressChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
notifyAddressChanged();
}
});
}
protected JTextField getAddressSpaceTextField() {
return spaceField;
private void hexModeChanged(boolean hexMode) {
this.assumeHex = hexMode;
addressEvaluator.setAssumeHex(hexMode);
notifyAddressChanged();
}
private String removeLeadingZeros(String addressString) {
// if it has a colon, then is is a segmented address, don't mess with it.
if (addressString.indexOf(":") >= 0) {
return addressString;
}
for (int i = 0; i < addressString.length(); i++) {
if (addressString.charAt(i) != '0') {
return addressString.substring(i);
}
}
return "0";
}
private void updateAddressSpaceCombo() {
notificationsEnabled = false;
try {
addressSpaceField.updateAddressSpaces(addressEvaluator.getAddressFactory());
}
finally {
notificationsEnabled = true;
}
addRemoveAdressSpaceField();
}
private void addRemoveAdressSpaceField() {
remove(addressSpaceField);
if (addressSpaceField.getSpaceCount() > 1) {
add(addressSpaceField, BorderLayout.WEST);
}
revalidate();
}
private void notifyAddressChanged() {
if (notificationsEnabled) {
try {
Address address = getAddressWithExceptions();
addressChangedConsumer.accept(address);
}
catch (ExpressionException e) {
addressChangedConsumer.accept(null);
addressErrorConsumer.accept(e.getMessage());
}
}
}
private class AddressSpaceField extends JPanel {
private JComboBox<AddressSpace> combo;
private JTextField uneditableSpaceField;
private CardLayout layout;
private boolean editable = true;
private AddressSpaceField() {
layout = new CardLayout();
setLayout(layout);
combo = new GComboBox<>();
combo.setName("JComboBox");//for JUnits...
combo.getAccessibleContext().setAccessibleName("Address Space");
combo.addActionListener(ev -> addressSpaceChanged());
add(combo, "combo");
uneditableSpaceField = new JTextField("");
uneditableSpaceField.setEnabled(false);
add(uneditableSpaceField, "text");
}
private void addressSpaceChanged() {
AddressSpace space = (AddressSpace) combo.getSelectedItem();
addressEvaluator.setPreferredAddressSpace(space);
notifyAddressChanged();
}
private void setEditable(boolean state) {
this.editable = state;
updateLayout();
}
private void updateLayout() {
boolean showCombo = isEnabled() && editable;
layout.show(this, showCombo ? "combo" : "text");
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateLayout();
}
private int getSpaceCount() {
return combo.getModel().getSize();
}
private void updateAddressSpaces(AddressFactory addressFactory) {
ComboBoxModel<AddressSpace> model = createAddressSpaceModel(addressFactory);
combo.setModel(model);
AddressSpace defaultAddressSpace = addressFactory.getDefaultAddressSpace();
if (addressSpaceFilter.test(defaultAddressSpace)) {
setAddressSpace(defaultAddressSpace);
}
else {
setAddressSpace(model.getElementAt(0));
}
}
private AddressSpace getAddressSpace() {
return (AddressSpace) combo.getSelectedItem();
}
private void setAddressSpace(AddressSpace addressSpace) {
combo.setSelectedItem(addressSpace);
String name = addressSpace.getName();
uneditableSpaceField.setText(name);
invalidate();
}
private void setComponentsBorder(Border border) {
combo.setBorder(border);
uneditableSpaceField.setBorder(border);
}
private ComboBoxModel<AddressSpace> createAddressSpaceModel(AddressFactory factory) {
AddressSpace[] spaces = factory.getAddressSpaces();
Arrays.sort(spaces, ADDRESS_SPACE_SORT_COMPARATOR);
DefaultComboBoxModel<AddressSpace> model = new DefaultComboBoxModel<>();
for (AddressSpace space : spaces) {
if (!addressSpaceFilter.test(space)) {
continue;
}
model.addElement(space);
}
return model;
}
}
}

View file

@ -67,10 +67,7 @@ public class AddressSetEditorPanel extends JPanel {
JLabel minLabel = new GDLabel("Min:");
minLabel.setToolTipText("Enter minimum address to add or remove");
minAddressPanel.add(minLabel, BorderLayout.WEST);
minAddressField = new AddressInput();
minAddressField.setAddressFactory(addressFactory);
ChangeListener listener = e -> validateAddRemoveButton();
minAddressField.addChangeListener(listener);
minAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton());
minAddressPanel.add(minAddressField, BorderLayout.CENTER);
JPanel maxAddressPanel = new JPanel();
@ -78,9 +75,7 @@ public class AddressSetEditorPanel extends JPanel {
JLabel maxLabel = new GDLabel("Max:");
maxLabel.setToolTipText("Enter maximum address to add or remove");
maxAddressPanel.add(maxLabel, BorderLayout.WEST);
maxAddressField = new AddressInput();
maxAddressField.setAddressFactory(addressFactory);
maxAddressField.addChangeListener(listener);
maxAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton());
maxAddressPanel.add(maxAddressField, BorderLayout.CENTER);
maxAddressPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
addRangeButton = new GButton(ADD_ICON);

View file

@ -338,7 +338,7 @@ public class OptionsEditorPanel extends JPanel {
return null;
}
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
AddressInput addressInput = new AddressInput();
AddressInput addressInput = new AddressInput(a -> option.setValue(a));
addressInput.setName(option.getName());
Address addr = (Address) option.getValue();
if (addr == null && addressFactory != null) {
@ -347,7 +347,6 @@ public class OptionsEditorPanel extends JPanel {
}
addressInput.setAddressFactory(addressFactory);
addressInput.setAddress(addr);
addressInput.addChangeListener(e -> option.setValue(addressInput.getAddress()));// addressInput.addActionListener(e -> option.setValue(addressInput.getAddress()));
return addressInput;
}
}

View file

@ -18,9 +18,9 @@ package ghidra.app.util.cparser.CPP;
import java.util.*;
import generic.expressions.ExpressionEvaluator;
import ghidra.app.util.cparser.CPP.PreProcessor.PPToken;
import ghidra.program.model.data.*;
import ghidra.program.util.AddressEvaluator;
import ghidra.util.Msg;
/**
@ -288,7 +288,6 @@ public class DefineTable {
sublist.addAll(initialList);
}
// don't replace an infinite number of times. Fail safe for possible ininite loop
while (pos < buf.length() && replaceCount < ARBITRARY_MAX_REPLACEMENTS) {
// clear list of used macros when move past replacement area
@ -353,7 +352,8 @@ public class DefineTable {
return true;
}
int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList<String> sublist, boolean initialList) {
int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList<String> sublist,
boolean initialList) {
String replacementString = null;
if (sublist == null) {
@ -455,7 +455,7 @@ public class DefineTable {
// Handle "..." varargs
// if last argument is ellipsis, then is varargs, replace the rest of the params
String curArgName = argv.elementAt(index).image;
if (index == argv.size()-1 && VARARG_ELLIPSIS.equals(curArgName)) {
if (index == argv.size() - 1 && VARARG_ELLIPSIS.equals(curArgName)) {
isVarArg = true;
// Replace __VA_ARGS__ with the rest of params
curArgName = "__VA_ARGS__";
@ -550,22 +550,23 @@ public class DefineTable {
return;
}
int lparen = buf.indexOf("(", optIdx+1);
int lparen = buf.indexOf("(", optIdx + 1);
if (lparen < 0) {
return;
}
int rparen = buf.indexOf(")",lparen+1);
int rparen = buf.indexOf(")", lparen + 1);
if (rparen < 0) {
return;
}
// get in between string.
String replarg = buf.substring(lparen+1, rparen);
String replarg = buf.substring(lparen + 1, rparen);
if (hadVarArgs) {
buf.replace(optIdx, rparen+1, replarg);
} else {
buf.replace(optIdx, rparen+1, "");
buf.replace(optIdx, rparen + 1, replarg);
}
else {
buf.replace(optIdx, rparen + 1, "");
}
}
@ -720,7 +721,7 @@ public class DefineTable {
if (lvalue == null) {
try {
lvalue = AddressEvaluator.evaluateToLong(strValue);
lvalue = ExpressionEvaluator.evaluateToLong(strValue);
}
catch (Exception exc) {
// ignore didn't parse well
@ -738,7 +739,8 @@ public class DefineTable {
dtMgr.endTransaction(transactionID, true);
}
public void populateDefineEquate(DataTypeManager openDTMgrs[], DataTypeManager dtMgr, String category, String prefix, String defName, long value) {
public void populateDefineEquate(DataTypeManager openDTMgrs[], DataTypeManager dtMgr,
String category, String prefix, String defName, long value) {
String enumName = prefix + defName;
// Start the Enum at 8, then resize to fit the value
@ -757,27 +759,27 @@ public class DefineTable {
dtMgr.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER);
}
private DataType resolveDataType(DataTypeManager openDTMgrs[], CategoryPath path, DataType dt) {
if (openDTMgrs == null) {
return dt;
}
// If the exact data type exists in any open DTMgr, use the open DTmgr type
// instead
private DataType resolveDataType(DataTypeManager openDTMgrs[], CategoryPath path, DataType dt) {
if (openDTMgrs == null) {
return dt;
}
// If the exact data type exists in any open DTMgr, use the open DTmgr type
// instead
for (int i = 0; i < openDTMgrs.length; i++) {
// look for the data type by name
// equivalent, return it
// look for the data type by category
// equivalent, return it
DataType candidateDT = openDTMgrs[i].getDataType(dt.getCategoryPath(), dt.getName());
for (int i = 0; i < openDTMgrs.length; i++) {
// look for the data type by name
// equivalent, return it
// look for the data type by category
// equivalent, return it
DataType candidateDT = openDTMgrs[i].getDataType(dt.getCategoryPath(), dt.getName());
if (candidateDT != null && candidateDT.isEquivalent(candidateDT)) {
return candidateDT;
}
}
if (candidateDT != null && candidateDT.isEquivalent(candidateDT)) {
return candidateDT;
}
}
return dt;
}
return dt;
}
public String expandDefine(String defName) {
// don't worry about macros

View file

@ -25,19 +25,17 @@ import docking.DockingWindowManager;
import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
public class AskAddrDialog extends DialogComponentProvider {
private boolean isCanceled;
private AddressInput addrInput;
public AskAddrDialog(final String title, final String message, AddressFactory af,
public AskAddrDialog(final String title, final String message, Program program,
Address lastAddr) {
super(title, true, true, true, false);
addrInput = new AddressInput();
addrInput.setAddressFactory(af);
addrInput.selectDefaultAddressSpace();
addrInput = new AddressInput(program);
if (lastAddr != null) {
addrInput.setAddress(lastAddr);
}

View file

@ -251,7 +251,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> {
addrField.setValue("0x200");
addrField.setText("0x200");
nameField.setText(".test");
lengthField.setText("0x100");
commentField.setText("this is a block test");
@ -319,7 +319,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> {
addrField.setValue("0x01001200");
addrField.setText("0x01001200");
nameField.setText(".test");
lengthField.setText("0x100");
commentField.setText("this is a block test");
@ -354,7 +354,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> {
addrField.setValue("xxxxx");
addrField.setText("xxxxx");
nameField.setText(".test");
lengthField.setText("0x100");
commentField.setText("this is a block test");
@ -364,7 +364,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Please enter a valid Start Address", msg);
assertEquals("Invalid Address: Could not evaluate token \"xxxxx\"", msg);
assertFalse(okButton.isEnabled());
close(d);
}
@ -768,7 +768,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Please enter a source address for the bit block", msg);
runSwing(() -> addrField.setValue("01001000"));
runSwing(() -> addrField.setText("01001000"));
}
else {
assertTrue(okButton.isEnabled());
@ -844,7 +844,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Please enter a source address for the bit block", msg);
runSwing(() -> addrField.setValue("01001000"));
runSwing(() -> addrField.setText("01001000"));
}
else {
assertTrue(okButton.isEnabled());
@ -1040,7 +1040,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
// move the block to 0x300
Address newStart = startAddr.getAddressSpace().getAddressInThisSpaceOnly(0x300);
Address newEnd = startAddr.getAddressSpace().getAddressInThisSpaceOnly(0x3ff);
runSwing(() -> startField.setValue(newStart));
runSwing(() -> startField.simulateAddressChanged(newStart));
assertEquals(newEnd, endField.getAddress());
assertTrue(okButton.isEnabled());
@ -1110,7 +1110,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
// enter an invalid address
runSwing(() -> startField.setValue(getAddr(0x0300).toString() + "gggg"));
runSwing(() -> startField.setText(getAddr(0x0300).toString() + "gggg"));
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Invalid Address", msg);
@ -1168,7 +1168,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
// enter an invalid address
runSwing(() -> endField.setValue(getAddr(0x0300).toString() + "gggg"));
runSwing(() -> endField.setText(getAddr(0x0300).toString() + "gggg"));
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Invalid Address", msg);
@ -1227,8 +1227,8 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
// enter an invalid address
runSwing(() -> {
startField.setValue(getAddr(0x1000).toString());
endField.setValue(getAddr(0x10).toString());
startField.setText(getAddr(0x1000).toString());
endField.setText(getAddr(0x10).toString());
});
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
@ -1287,7 +1287,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest
assertFalse(okButton.isEnabled());
// enter an invalid address
runSwing(() -> startField.setValue("00000000"));
runSwing(() -> startField.setText("00000000"));
assertFalse(okButton.isEnabled());
String msg = findLabelStr(d.getComponent(), "statusLabel");
assertEquals("Block is already at 00000000", msg);

View file

@ -231,7 +231,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockOneEnd.setValue("01003000"));
runSwing(() -> blockOneEnd.setText("01003000"));
assertEquals(0x2001, blockOneLength.getValue().longValue());
assertEquals(getAddr(0x01003001), blockTwoStart.getAddress());
assertEquals("010075ff", blockTwoEnd.getText());
@ -277,7 +277,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockTwoStart.setValue("01003000"));
runSwing(() -> blockTwoStart.setText("01003000"));
assertEquals(0x2000, blockOneLength.getValue().longValue());
assertEquals(getAddr(0x01002fff), blockOneEnd.getAddress());
assertEquals("010075ff", blockTwoEnd.getText());
@ -357,7 +357,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(AddressInput) findComponentByName(d.getComponent(), "BlockOneEnd");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockOneEnd.setValue("01000"));
runSwing(() -> blockOneEnd.setText("01000"));
assertFalse(okButton.isEnabled());
assertEquals("End address must be greater than start",
findLabelStr(d.getComponent(), "statusLabel"));
@ -378,7 +378,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(AddressInput) findComponentByName(d.getComponent(), "BlockTwoStart");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockTwoStart.setValue("01000"));
runSwing(() -> blockTwoStart.setText("01000"));
assertFalse(okButton.isEnabled());
assertEquals("Start address must be greater than original block start (01001000)",
findLabelStr(d.getComponent(), "statusLabel"));
@ -531,7 +531,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("00002000"));
runSwing(() -> start.setText("00002000"));
assertEquals("0x1005600", length.getText());
assertTrue(okButton.isEnabled());
@ -579,7 +579,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
assertNotNull(length);
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("01201000"));
runSwing(() -> start.setText("01201000"));
assertFalse(okButton.isEnabled());
assertEquals("Start must be less than 01001000",
findLabelStr(d.getComponent(), "statusLabel"));
@ -681,7 +681,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
JTextField end = (JTextField) findComponentByName(d.getComponent(), "EndAddress");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("01008000"));
runSwing(() -> start.setText("01008000"));
assertEquals("0100f3ff", end.getText());
assertTrue(okButton.isEnabled());
@ -740,7 +740,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> end.setValue("01007700"));
runSwing(() -> end.setText("01007700"));
assertEquals("0x6701", length.getText());
assertTrue(okButton.isEnabled());
@ -798,7 +798,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
AddressInput end = (AddressInput) findComponentByName(d.getComponent(), "EndAddress");
JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> end.setValue("01007000"));
runSwing(() -> end.setText("01007000"));
assertFalse(okButton.isEnabled());
assertEquals("End must be greater than 010075ff",
findLabelStr(d.getComponent(), "statusLabel"));

View file

@ -0,0 +1,219 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import static org.junit.Assert.*;
import java.util.function.Predicate;
import javax.swing.JFrame;
import org.junit.*;
import ghidra.program.database.*;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
public class AddressInputTest extends AbstractGhidraHeadedIntegrationTest {
private JFrame frame;
private AddressInput field;
private ProgramDB programOneSpace;
private ProgramDB programMultiSpaces;
private volatile Address changedAddress;
private volatile String errorMessage;
@Before
public void setUp() throws Exception {
programOneSpace = createDefaultProgram("oneSpace", ProgramBuilder._TOY, this);
programMultiSpaces = createDefaultProgram("mulitSpaces", ProgramBuilder._8051, this);
field = new AddressInput(programOneSpace, this::addressChanged);
field.setAddressErrorConsumer(this::addressError);
frame = new JFrame("Test");
frame.getContentPane().add(field);
frame.pack();
frame.setVisible(true);
}
@After
public void tearDown() throws Exception {
frame.setVisible(false);
programOneSpace.release(this);
programMultiSpaces.release(this);
}
@Test
public void testDefaultState() {
assertTrue(getText().isBlank());
assertNull(getAddress());
}
@Test
public void testHexOrDecimalMode() {
setText("100"); // should have defaulted to hex mode
assertEquals(addr(0x100), getAddress());
setHexMode(false);
assertEquals(addr(100), field.getAddress());
setHexMode(true);
assertEquals(addr(0x100), field.getAddress());
}
@Test
public void testSwitchingBetweenOneAndMultiSpaces() {
assertEquals(1, field.getComponentCount());
setProgram(programMultiSpaces);
assertEquals(2, field.getComponentCount());
setProgram(programOneSpace);
assertEquals(1, field.getComponentCount());
}
@Test
public void testSetAddress() {
setAddress(addr(0x100));
assertEquals("100", getText());
setHexMode(false);
setAddress(addr(0x100));
assertEquals("0x100", getText());
}
@Test
public void testSettingAddressChangesResultingSpace() {
setProgram(programMultiSpaces);
setText("100");
Address a = getAddress();
assertEquals("CODE:0100", a.toString(true));
Address newAddress = addr(programMultiSpaces, "EXTMEM", 0x20);
setAddress(newAddress);
setText("100");
a = getAddress();
assertEquals("EXTMEM:0100", a.toString(true));
}
@Test
public void testGetAddressWithBadExpression() {
setText("100+ (");
assertNull(getAddress());
}
@Test
public void testWorksWithJustAddressFactory() {
setAddressFactory(programMultiSpaces.getAddressFactory());
setText("100");
assertEquals(addr(programMultiSpaces, 0x100), getAddress());
}
@Test
public void testGetSelectedAddressSpace() {
setProgram(programMultiSpaces);
ProgramAddressFactory factory = programMultiSpaces.getAddressFactory();
AddressSpace codeSpace = factory.getAddressSpace("CODE");
AddressSpace extmemSpace = factory.getAddressSpace("EXTMEM");
assertEquals(codeSpace, getAddressSpaceInField());
setAddress(addr(programMultiSpaces, "EXTMEM", 100));
assertEquals(extmemSpace, getAddressSpaceInField());
}
@Test
public void testSpaceFilter() {
setProgram(programMultiSpaces);
setSpaceFilter(s -> s.getName().equals("EXTMEM"));
assertEquals(1, field.getComponentCount());
setText("100");
assertEquals(addr(programMultiSpaces, "EXTMEM", 0x100), getAddress());
}
@Test
public void testAddressChangeConsumer() {
setText("200");
assertEquals(addr(0x200), changedAddress);
setText("300");
assertEquals(addr(0x300), changedAddress);
setText("lkjlkj");
assertNull(changedAddress);
}
@Test
public void testAddressErrorConsmer() {
errorMessage = null;
setText("200");
assertNull(errorMessage);
setText("xyz");
assertEquals("Could not evaluate token \"xyz\"", errorMessage);
}
private Address addr(long offset) {
return addr(programOneSpace, offset);
}
private Address addr(ProgramDB p, long offset) {
return p.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
private Address addr(ProgramDB p, String spaceName, long offset) {
AddressSpace space = p.getAddressFactory().getAddressSpace(spaceName);
return space.getAddress(offset);
}
private void setProgram(Program p) {
runSwing(() -> field.setProgram(p));
}
private void setAddressFactory(AddressFactory factory) {
runSwing(() -> field.setAddressFactory(factory));
}
private void setSpaceFilter(Predicate<AddressSpace> filter) {
runSwing(() -> field.setAddressSpaceFilter(filter));
}
private void setText(String value) {
runSwing(() -> field.setText(value));
}
private String getText() {
return runSwing(() -> field.getText());
}
private void setAddress(Address a) {
runSwing(() -> field.setAddress(a));
}
private Address getAddress() {
return runSwing(() -> field.getAddress());
}
private AddressSpace getAddressSpaceInField() {
return runSwing(() -> field.getAddressSpace());
}
private void setHexMode(boolean hexMode) {
runSwing(() -> field.setAssumeHex(hexMode));
waitForSwing();
}
private void addressChanged(Address address) {
this.changedAddress = address;
}
private void addressError(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View file

@ -21,7 +21,6 @@ import org.junit.Test;
import docking.widgets.values.AbstractValue;
import ghidra.app.util.AddressInput;
import ghidra.features.base.values.AddressValue;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
@ -144,7 +143,7 @@ public class AddressValueTest extends AbstractValueIntegrationTest {
protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent();
addressInput.setValue(text);
addressInput.setText(text);
});
}
}

View file

@ -43,7 +43,7 @@ public class ValuesMapDialogParseErrorTest extends AbstractValueIntegrationTest
protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent();
addressInput.setValue(text);
addressInput.setText(text);
});
}
}

View file

@ -17,103 +17,146 @@ package ghidra.program.util;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.*;
import generic.test.AbstractGenericTest;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
/**
*
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class AddressEvaluatorTest extends AbstractGhidraHeadedIntegrationTest {
public class AddressEvaluatorTest extends AbstractGenericTest {
AddressFactory addrFactory;
private Program program;
private ProgramBuilder builder;
private Symbol entry;
private Symbol entryInFoo;
public AddressEvaluatorTest() {
super();
}
private Address addr(String address) {
return addrFactory.getAddress(address);
@Before
public void setUp() throws Exception {
builder = new ProgramBuilder("Test", ProgramBuilder._TOY_LE, this);
builder.createMemory("TestBlock", "0x100", 100);
program = builder.getProgram();
entry = builder.createLabel("0x100", "entry");
builder.createNamespace("foo");
entryInFoo = builder.createLabel("0x103", "entry", "foo");
}
@After
public void tearDown() {
program.release(this);
}
@Test
public void testEval() throws Exception {
Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this);
addrFactory = p.getAddressFactory();
int txId = p.startTransaction("Test");
try {
assertEquals(addr("0x19"), AddressEvaluator.evaluate(p, "(2+3)*5"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+3*5"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+(3*5)"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "(2+3*5)"));
assertEquals(addr("0x16"), AddressEvaluator.evaluate(p, "0x11+5"));
assertEquals(addr("0x02"), AddressEvaluator.evaluate(p, "2-1+1"));
assertEquals(addr("0x5"), AddressEvaluator.evaluate(p, "5"));
assertEquals(addr("0x3"), AddressEvaluator.evaluate(p, "0-5+8"));
assertEquals(addr("0x3"), AddressEvaluator.evaluate(p, "-5+8"));
assertEquals(addr("0xfffffffB"), AddressEvaluator.evaluate(p, "-5"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "3+(5+(3*2)+(3))"));
assertEquals(addr("0xff00"), AddressEvaluator.evaluate(p, "0xffff ^ 0xff"));
assertEquals(addr("0x123f"), AddressEvaluator.evaluate(p, "0xffff & 0x123f"));
assertEquals(addr("0x1234"), AddressEvaluator.evaluate(p, "0x1200 | 0x0034"));
assertEquals(addr("0xffffffff"), AddressEvaluator.evaluate(p, "~ 0x0"));
assertEquals(addr("0x1201"), AddressEvaluator.evaluate(p, "0x1200 | ~(0xfffffffe)"));
assertEquals(addr("0x480"), AddressEvaluator.evaluate(p, "0x1200 >> 2"));
assertEquals(addr("0x1200"), AddressEvaluator.evaluate(p, "0x480 << 2"));
public void testLongValueExpression() {
assertEval(addr("0x19"), "(2+3)*5");
assertEval(addr("0x11"), "2+3*5");
assertEval(addr("0x11"), "2+(3*5)");
assertEval(addr("0x3"), "0-5+8");
assertEval(addr("0x3"), "-5+8");
assertEval(addr("0xfffffffB"), "-5");
assertEval(addr("0x11"), "3+(5+(3*2)+(3))");
}
assertEquals(addr("0x1"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x2)"));
assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x1)"));
assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x1)"));
@Test
public void testAssumesHex() {
assertEval(addr("0x30"), "20 + 10");
assertEval(addr("0xf1"), "e1+10");
}
assertEquals(addr("0x1"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) >= 0x1)"));
assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) <= 0x1)"));
@Test
public void testAcceptsHexPrefix() {
assertEval(addr("0x16"), "0x11+5");
assertEval(addr("0x35"), "20+0x15");
}
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "(4ul)"));
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "(4UL)"));
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "( 4l )"));
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "(4L)"));
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "(4u )"));
assertEquals(addr("0x4"), AddressEvaluator.evaluate(p, "( 4U)"));
@Test
public void testBitWiseExpressions() {
assertEquals(null, AddressEvaluator.evaluate(p, "( 4P)"));
assertEval(addr("0xff00"), "0xffff ^ 0xff");
assertEval(addr("0x123f"), "0xffff & 0x123f");
assertEval(addr("0x1234"), "0x1200 | 0x0034");
assertEval(addr("0xffffffff"), "~ 0x0");
assertEval(addr("0x1201"), "0x1200 | ~(0xfffffffe)");
assertEval(addr("0x480"), "0x1200 >> 2");
assertEval(addr("0x1200"), "0x480 << 2");
assertEval(addr("0x103"), "0x100 | 0x1 | ~(~0x2)");
}
Symbol s = p.getSymbolTable().createLabel(addr("0x100"), "entry", SourceType.IMPORTED);
Address a = s.getAddress();
a = a.add(10);
assertEquals(a, AddressEvaluator.evaluate(p, "entry+5*2"));
assertEquals(addr("0x101"), AddressEvaluator.evaluate(p, "entry + (entry == 0x100)"));
assertEquals(addr("0x500"), AddressEvaluator.evaluate(p,
"entry + (entry == 0x100) * 0x400 + (entry < 0x100) * 0x500"));
assertEquals(addr("0x600"), AddressEvaluator.evaluate(p,
"entry + (entry > 0x100) * 0x400 + (entry <= 0x100) * 0x500"));
}
finally {
p.endTransaction(txId, true);
p.release(this);
}
@Test
public void testLogicalExpressions() {
assertEval(addr("0x1"), "(((0x1 | 0x2) & 0x2) == 0x2)");
assertEval(addr("0x0"), "(((0x1 | 0x2) & 0x2) == 0x1)");
assertEval(addr("0x0"), "(((0x1 | 0x2) & 0x2) == 0x1)");
assertEval(addr("0x1"), "(((0x1 | 0x2) & 0x2) >= 0x1)");
assertEval(addr("0x0"), "(((0x1 | 0x2) & 0x2) <= 0x1)");
}
@Test
public void testAlternateNumberDecorations() {
assertEval(addr("0x4"), "(4ul)");
assertEval(addr("0x4"), "(4UL)");
assertEval(addr("0x4"), "( 4l )");
assertEval(addr("0x4"), "(4L)");
assertEval(addr("0x4"), "(4u )");
assertEval(addr("0x4"), "( 4U)");
}
@Test
public void testInvalidInput() {
assertEval(null, "( 4P)");
}
@Test
public void testShift() {
assertEval(addr("0x80"), "0x100 >> 1");
assertEval(addr("0x400"), "0x100 << 2");
}
@Test
public void testSymbolLookup() {
assertEval(entry.getAddress(), "entry");
assertEval(entry.getAddress().add(10), "entry+5*2");
assertEval(addr("0x101"), "entry + (entry == 0x100)");
assertEval(addr("0x500"), "entry + (entry == 0x100) * 0x400 + (entry < 0x100) * 0x500");
assertEval(addr("0x600"), "entry + (entry > 0x100) * 0x400 + (entry <= 0x100) * 0x500");
}
@Test
public void testSymbolInNamespaceLookup() {
assertEval(entryInFoo.getAddress(), "foo::entry");
assertEval(entryInFoo.getAddress().add(10), "foo::entry+5*2");
assertEval(null, "bar::entry");
}
@Test
public void testSymbolShift() {
assertEval(addr("0x80"), "entry >> 1");
assertEval(addr("0x400"), "entry << 2");
}
@Test
public void testMemoryBlockOffset() {
assertEval(addr("0x110"), "TestBlock+10");
}
@Test
public void testMultiAddrSpace() throws Exception {
Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this);
addrFactory = p.getAddressFactory();
try {
assertEquals(addr("0x19"), AddressEvaluator.evaluate(p, "(2+3)*5"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+3*5"));
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+(3*5)"));
assertEquals(addr("RAM:15"), AddressEvaluator.evaluate(p, "RAM:2 + 0x13"));
}
finally {
p.release(this);
}
assertEval(addr("0x15"), "ram:2 + 0x13");
assertEval(addr("register:0x15"), "register:2 + 0x13");
}
private void assertEval(Address addr, String input) {
assertEquals(addr, AddressEvaluator.evaluate(program, input));
}
private Address addr(String address) {
return program.getAddressFactory().getAddress(address);
}
}

View file

@ -81,12 +81,11 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider
if (provider instanceof ProgramByteViewerComponentProvider) {
Program program = ((ProgramByteViewerComponentProvider) provider).getProgram();
if (program != null) {
addressInputField = new AddressInput();
addressInputField.setAddressFactory(program.getAddressFactory());
addressInputField.showAddressSpaceCombo(false);
addressInputField.setAddress(getAlignmentAddress());
Address alignment = getAlignmentAddress();
addressInputField = new AddressInput(program, a -> update());
addressInputField.setAddressSpaceFilter(s -> s == alignment.getAddressSpace());
addressInputField.setAddress(alignment);
panel.add(addressInputField);
addressInputField.addChangeListener(this);
addressInputField.setAccessibleName("Alignment Address");
}
}
@ -214,13 +213,13 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider
}
private boolean hasValidFieldValues() {
if (addressInputField.getValue().length() == 0) {
if (addressInputField.getText().length() == 0) {
setStatusText("Enter an alignment address");
return false;
}
Address alignmentAddress = addressInputField.getAddress();
if (alignmentAddress == null) {
setStatusText("Invalid alignment address:" + addressInputField.getValue());
setStatusText("Invalid alignment address:" + addressInputField.getText());
return false;
}
BigInteger bytesPerLine = bytesPerLineField.getValue();

View file

@ -553,7 +553,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
// current offset is 0
assertEquals(program.getMinAddress(), ai.getAddress());
runSwing(() -> ai.setValue("0100100b"));
runSwing(() -> ai.setText("0100100b"));
pressButtonByText(d.getComponent(), "OK");
// verify that offset label on the plugin shows '5'
assertEquals(5, plugin.getProvider().getOffset());
@ -587,7 +587,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
// current offset is 0
assertEquals(program.getMinAddress(), ai.getAddress());
runSwing(() -> ai.setValue("0000:0c06"));
runSwing(() -> ai.setText("0000:0c06"));
pressButtonByText(d.getComponent(), "OK");
assertEquals(10, plugin.getProvider().getOffset());

View file

@ -55,8 +55,7 @@ public class EditableListingAddress extends DisplayableListingAddress implements
private void buildPanel() {
setLayout(new PairLayout(5, 5, 50));
addressField = new AddressInput();
addressField.setAddressFactory(program.getAddressFactory());
addressField = new AddressInput(program);
if (address != null) {
addressField.setAddress(address);
}
@ -78,7 +77,7 @@ public class EditableListingAddress extends DisplayableListingAddress implements
Address selectedAddress = addressField.getAddress();
if (selectedAddress == null) {
throw new InvalidInputException(
"\"" + addressField.getValue() + "\" is not a valid address.");
"\"" + addressField.getText() + "\" is not a valid address.");
}
if (!program.getMemory().contains(selectedAddress)) {
throw new InvalidInputException(

View file

@ -24,7 +24,6 @@ import docking.widgets.label.GDLabel;
import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout;
@ -32,7 +31,6 @@ import ghidra.util.layout.PairLayout;
public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
private Program program;
private AddressFactory addressFactory;
private AddressRangeListener listener;
private JPanel addressRangePanel;
@ -45,7 +43,6 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
AddressRangeListener listener) {
super(programIndicator + " Address Range", true, true, true, false);
this.program = program;
addressFactory = program.getAddressFactory();
this.listener = listener;
setHelpLocation(new HelpLocation(HelpTopics.LABEL, "AddEditDialog"));
addWorkPanel(createAddressRangePanel());
@ -68,8 +65,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
minLabel.setToolTipText("Enter minimum address to add or remove");
addressRangePanel.add(minLabel);
minAddressField = new AddressInput();
minAddressField.setAddressFactory(addressFactory);
minAddressField = new AddressInput(program);
Dimension minPreferredSize = getPreferredSize();
minPreferredSize.width = 200;
minAddressField.setPreferredSize(minPreferredSize);
@ -79,8 +75,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
maxLabel.setToolTipText("Enter maximum address to add or remove");
addressRangePanel.add(maxLabel);
maxAddressField = new AddressInput();
maxAddressField.setAddressFactory(addressFactory);
maxAddressField = new AddressInput(program);
Dimension maxPreferredSize = getPreferredSize();
maxPreferredSize.width = 200;
minAddressField.setPreferredSize(maxPreferredSize);

View file

@ -0,0 +1,114 @@
/* ###
* 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 docking.widgets.textfield;
import java.awt.*;
import java.awt.event.*;
import java.util.function.Consumer;
import javax.swing.JTextField;
import javax.swing.ToolTipManager;
import docking.DockingUtils;
import docking.util.GraphicsUtils;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.Gui;
/**
* Overrides the JTextField mainly to allow hint painting for the current radix mode.
*/
public class HexDecimalModeTextField extends JTextField {
private static final String FONT_ID = "font.input.hint";
private int hintWidth;
private boolean isHexMode;
private boolean showNumbericDecoration = true;
public HexDecimalModeTextField(int columns, Consumer<Boolean> modeConsumer) {
super(columns);
FontMetrics fontMetrics = getFontMetrics(Gui.getFont(FONT_ID));
String mode = isHexMode ? "Hex" : "Dec";
hintWidth = fontMetrics.stringWidth(mode);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_M && DockingUtils.isControlModifier(e)) {
isHexMode = !isHexMode;
modeConsumer.accept(isHexMode);
repaint();
}
}
});
// make sure tooltips will be activated
ToolTipManager.sharedInstance().registerComponent(this);
}
@Override
public String getToolTipText(MouseEvent event) {
int hintStart = getBounds().width - hintWidth;
if (event.getX() > hintStart) {
String key = DockingUtils.CONTROL_KEY_NAME;
return "Press '" + key + "-M' to toggle Hex or Decimal Mode";
}
return null;
}
public void setHexMode(boolean hexMode) {
this.isHexMode = hexMode;
}
/**
* Turns on or off the faded text that displays the field's radix mode (hex or decimal).
*
* @param show true to show the radix mode.
*/
public void setShowNumberMode(boolean show) {
this.showNumbericDecoration = show;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (!showNumbericDecoration) {
return;
}
Font savedFont = g.getFont();
g.setFont(Gui.getFont(FONT_ID));
g.setColor(Messages.HINT);
Dimension size = getSize();
Insets insets = getInsets();
int x;
if (getHorizontalAlignment() == RIGHT) {
x = insets.left;
}
else {
x = size.width - insets.right - hintWidth;
}
int y = size.height - insets.bottom - 1;
String mode = isHexMode ? "Hex" : "Dec";
GraphicsUtils.drawString(this, g, mode, x, y);
g.setFont(savedFont);
}
}

View file

@ -15,20 +15,16 @@
*/
package docking.widgets.textfield;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.ActionListener;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.event.*;
import javax.swing.text.*;
import docking.DockingUtils;
import docking.util.GraphicsUtils;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.Gui;
import ghidra.util.SystemUtilities;
/**
@ -63,12 +59,11 @@ import ghidra.util.SystemUtilities;
*/
public class IntegerTextField {
private JTextField textField;
private HexDecimalModeTextField textField;
private boolean isHexMode = false;
private boolean allowsNegative = true;
private boolean allowsHexPrefix = true;
private boolean showNumbericDecoration = true;
private BigInteger maxValue;
private BigInteger minValue;
@ -109,9 +104,13 @@ public class IntegerTextField {
* @param initialValue the initial value
*/
public IntegerTextField(int columns, BigInteger initialValue) {
textField = new MyTextField(columns);
textField = new HexDecimalModeTextField(columns, b -> textFieldHexModeChanged(b));
AbstractDocument document = (AbstractDocument) textField.getDocument();
document.setDocumentFilter(new HexDecimalDocumentFilter());
setValue(initialValue);
textField.getDocument().addDocumentListener(new DocumentListener() {
document.addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
@ -266,8 +265,7 @@ public class IntegerTextField {
* @param show true to show the radix mode.
*/
public void setShowNumberMode(boolean show) {
this.showNumbericDecoration = show;
textField.repaint();
textField.setShowNumberMode(show);
}
/**
@ -278,9 +276,15 @@ public class IntegerTextField {
* the current value from decimal to hex.
*/
public void setHexMode() {
BigInteger currentValue = getValue();
isHexMode = true;
updateTextField(currentValue);
BigInteger value = getValue();
setHexMode(true);
setValue(value);
}
private void setHexMode(boolean hexMode) {
this.isHexMode = hexMode;
textField.setHexMode(hexMode);
}
/**
@ -291,9 +295,9 @@ public class IntegerTextField {
* current value from hex to decimal.
*/
public void setDecimalMode() {
BigInteger currentValue = getValue();
isHexMode = false;
updateTextField(currentValue);
BigInteger value = getValue();
setHexMode(false);
setValue(value);
}
/**
@ -477,6 +481,12 @@ public class IntegerTextField {
textField.setHorizontalAlignment(alignment);
}
private void textFieldHexModeChanged(boolean hexMode) {
BigInteger value = getValue();
this.isHexMode = hexMode;
setValue(value);
}
private String computeTextForValue(BigInteger value) {
if (value == null) {
return "";
@ -525,15 +535,6 @@ public class IntegerTextField {
}
}
private void toggleMode() {
if (isHexMode) {
setDecimalMode();
}
else {
setHexMode();
}
}
private boolean passesMaxCheck(BigInteger value) {
if (value == null) {
return true;
@ -648,7 +649,7 @@ public class IntegerTextField {
if (isValidPrefix(valueString)) {
// When the input is valid, update the hex mode to match how the text was parsed.
// See parseAsHex variable comment above.
isHexMode = parseAsHex;
setHexMode(parseAsHex);
return true;
}
@ -661,7 +662,7 @@ public class IntegerTextField {
if (passesMaxCheck(value) && passesMinCheck(value)) {
// When the input is valid, update the hex mode to match how the text was parsed.
// See parseAsHex variable comment above.
isHexMode = parseAsHex;
setHexMode(parseAsHex);
return true;
}
}
@ -692,76 +693,4 @@ public class IntegerTextField {
// so we don't allow negatives
return value.signum() < 0;
}
/**
* Overrides the JTextField mainly to allow hint painting for the current radix mode.
*/
private class MyTextField extends JTextField {
private static final String FONT_ID = "font.input.hint";
private int hintWidth;
public MyTextField(int columns) {
super(columns);
FontMetrics fontMetrics = getFontMetrics(Gui.getFont(FONT_ID));
String mode = isHexMode ? "Hex" : "Dec";
hintWidth = fontMetrics.stringWidth(mode);
AbstractDocument document = (AbstractDocument) getDocument();
document.setDocumentFilter(new HexDecimalDocumentFilter());
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_M && DockingUtils.isControlModifier(e)) {
toggleMode();
repaint();
}
}
});
// make sure tooltips will be activated
ToolTipManager.sharedInstance().registerComponent(this);
}
@Override
public String getToolTipText(MouseEvent event) {
int hintStart = getBounds().width - hintWidth;
if (event.getX() > hintStart) {
String key = DockingUtils.CONTROL_KEY_NAME;
return "Press '" + key + "-M' to toggle Hex or Decimal Mode";
}
return null;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (!showNumbericDecoration) {
return;
}
Font savedFont = g.getFont();
g.setFont(Gui.getFont(FONT_ID));
g.setColor(Messages.HINT);
Dimension size = getSize();
Insets insets = getInsets();
int x;
if (getHorizontalAlignment() == RIGHT) {
x = insets.left;
}
else {
x = size.width - insets.right - hintWidth;
}
int y = size.height - insets.bottom - 1;
String mode = isHexMode ? "Hex" : "Dec";
GraphicsUtils.drawString(this, g, mode, x, y);
g.setFont(savedFont);
}
}
}

View file

@ -0,0 +1,24 @@
/* ###
* 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 generic.expressions;
/**
* Base marker interface for {@link ExpressionGrouper}, {@link ExpressionOperator},
* and {@link ExpressionValue}
*/
public interface ExpressionElement {
// marker interface
}

View file

@ -0,0 +1,509 @@
/* ###
* 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 generic.expressions;
import static generic.expressions.ExpressionGrouper.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import ghidra.util.NumericUtilities;
/**
* Class for evaluating numeric expressions. See
* {@link ExpressionOperator} for the full list of supported operators. All values are interpreted
* as longs. Optionally, an ExpressionEvalualuator can be constructed with a symbol evaluator that
* will be called on any string that can't be evaluated as an operator or number.
* <P>
* ExpressionEvaluators can operate in either decimal or hex mode. If in hex mode, all numbers are
* assumed to be hexadecimal values. In decimal mode, numbers are assumed to be decimal values, but
* hexadecimal values can still be specified by prefixing them with "0x".
* <P>
* There are also two convenience static methods that can be called to evaluate expressions. These
* methods will either return a Long value as the result or null if there was an error evaluating
* the expression. To get error messages related to parsing the expression, instantiate an
* ExpressionEvaluator and call {@link #parse(String)} which will throw a
* {@link ExpressionException} when the expression can't be evaluated.
*/
public class ExpressionEvaluator {
private static final String TOKEN_CHARS = "+-*/()<>|^&~ =!";
private boolean assumeHex = false;
private Function<String, ExpressionValue> evaluator;
/**
* Evaluates the given input as a Long value. This call assumes all numbers are decimal unless
* prefixed with a "0x".
* @param input the expression to be parsed into a Long value
* @return the resulting Long value or null if the expression could not be evaluated.
*/
public static Long evaluateToLong(String input) {
return evaluateToLong(input, false);
}
/**
* Evaluates the given input as a long value.
* @param input the expression to be parsed into a Long value
* @param assumeHex if true, numbers will be assumed to be hexadecimal values.
* @return the resulting Long value or null if the expression could not be evaluated.
*/
public static Long evaluateToLong(String input, boolean assumeHex) {
ExpressionEvaluator evaluator = new ExpressionEvaluator(assumeHex);
try {
return evaluator.parseAsLong(input);
}
catch (ExpressionException e) {
return null;
}
}
/**
* Constructs an ExpressionEvaluator in decimal mode.
*/
public ExpressionEvaluator() {
this(false);
}
/**
* Constructs an ExpressionEvaluator in either decimal or hex mode.
* @param assumeHex if true, the evaluator will assume all values are hexadecimal.
*/
public ExpressionEvaluator(boolean assumeHex) {
this(assumeHex, s -> null);
}
/**
* Constructs an ExpressionEvaluator in decimal mode with a given symbol evaluator.
* @param evaluator A function that can convert a string token into a value (Must be Long
* ExpressionValues, unless this is being called by a subclass that can handle other types
* of operand values)
*/
public ExpressionEvaluator(Function<String, ExpressionValue> evaluator) {
this(false, evaluator);
}
/**
* Constructs an ExpressionEvaluator in either decimal or hex mode with a given symbol
* evaluator.
* @param assumeHex if true, the evaluator will assume all values are hexadecimal.
* @param evaluator A function that can convert a string token into a value (Must be Long
* ExpressionValues, unless this is being called by a subclass that can handle other types
* of operand values)
*/
public ExpressionEvaluator(boolean assumeHex, Function<String, ExpressionValue> evaluator) {
this.assumeHex = assumeHex;
this.evaluator = Objects.requireNonNull(evaluator);
}
/**
* Parses the given expression input, expecting the result to be long value.
* @param input the expression string
* @return the long value result.
* @throws ExpressionException if the expression could not be evaluated to a long value.
*/
public long parseAsLong(String input) throws ExpressionException {
ExpressionValue expressionValue = parse(input);
if (expressionValue instanceof LongExpressionValue longValue) {
return longValue.getLongValue();
}
throw new ExpressionException("Expression did not evalute to a long! Got a " +
expressionValue.getClass() + " instead.");
}
/**
* Changes the hex/decimal mode.
* @param b if true, all numbers will be assumed to be hexadecimal
*/
public void setAssumeHex(boolean b) {
this.assumeHex = b;
}
protected ExpressionValue parse(String input) throws ExpressionException {
return this.parse(input, null);
}
protected ExpressionValue parse(String input, ExpressionValue initial)
throws ExpressionException {
List<ExpressionElement> list = new ArrayList<>();
// if there is a given initial value (used for relative expressions), add it to the
// sequential list of valid expression elements.
if (initial != null) {
list.add(initial);
}
// convert the text input into a list of valid expression elements
parseToList(input, list);
// evaluate the list of expression elements in operator precedence order.
return eval(list);
}
/**
* Parses the input string into a list of valid elements. When this method completes, the list
* will contain only valid operators, valid operand values, or group operators.
* @param input the input string to be parsed.
* @param list the list to populate with valid elements.
* @throws ExpressionException if any part of the input string can't be parsed into a valid
* expression element.
*/
private void parseToList(String input, List<ExpressionElement> list)
throws ExpressionException {
LookAheadTokenizer parser = new LookAheadTokenizer(input);
while (parser.hasMoreTokens()) {
String token = parser.getCurrentToken();
if (token.isBlank()) {
parser.advance(1);
}
else if (processGroupToken(list, token)) {
parser.advance(1);
}
else if (processOperator(list, token, parser.getNextToken())) {
ExpressionOperator op = getLastOperator(list);
parser.advance(op.size());
}
else if (processNumber(list, token)) {
parser.advance(1);
}
else if (processSymbol(list, token)) {
parser.advance(1);
}
else {
throw new ExpressionException("Could not evaluate token \"" + token + "\"");
}
}
if (list.isEmpty()) {
throw new ExpressionException("Expression is empty. Nothing to parse!");
}
}
/**
* Evaluates a list of valid expression elements into a single final value.
* @param list the list of valid expression elements.
* @return the final value the expression evaluates to
* @throws ExpressionException if sequence of expression elements is not in a valid order such
* that it evaluates to a single value. (such as two values not being separated by an operator)
*/
private ExpressionValue eval(List<ExpressionElement> list) throws ExpressionException {
// first evaluate any sub-lists grouped by parenthesis
processGroups(list);
// next process any unary operators
processUnaryOperators(list);
// final process binary operators in operator precedence order.
processBinaryOperators(list);
// if everything evaluated properly, there should only be one item left in the list
if (list.size() != 1) {
String result = list.stream().map(Object::toString).collect(Collectors.joining(" "));
throw new ExpressionException("Parse failed! Stopped at \"" + result + "\"");
}
ExpressionElement element = list.get(0);
if (element instanceof ExpressionValue ev) {
return ev;
}
throw new ExpressionException("Parse failed to evaluate to a value! Stopped at " + element);
}
private void processBinaryOperators(List<ExpressionElement> list) throws ExpressionException {
List<Set<ExpressionOperator>> ops = ExpressionOperator.getBinaryOperatorsByPrecedence();
// Each set in the list contains operators at the same precedence, so they all need
// to be processed at the same time so that they are processed left to right (which
// corresponds to the list order)
for (Set<ExpressionOperator> set : ops) {
processBinaryOperators(list, set);
}
}
private void processBinaryOperators(List<ExpressionElement> list,
Set<ExpressionOperator> operators) throws ExpressionException {
// can't have a valid binary operator at index 0, so start looking at index 1
int operatorIndex = findValidBinaryOperator(list, operators, 1);
while (operatorIndex >= 0) {
// we can safely cast here because we checked in the findValidBinaryOperator method
ExpressionOperator operator = (ExpressionOperator) list.get(operatorIndex);
ExpressionValue value1 = (ExpressionValue) list.get(operatorIndex - 1);
ExpressionValue value2 = (ExpressionValue) list.get(operatorIndex + 1);
ExpressionValue newValue = value1.applyBinaryOperator(operator, value2);
list.set(operatorIndex - 1, newValue);
list.subList(operatorIndex, operatorIndex + 2).clear();
// After the operator completed, the list has been changed and the sequence
// "value operator value" has been replace with the resulting value of the operation.
// Now look for the next operator in the current set of operators we are evaluating
operatorIndex = findValidBinaryOperator(list, operators, operatorIndex);
}
}
private int findValidBinaryOperator(List<ExpressionElement> list,
Set<ExpressionOperator> operators, int startIndex) {
// can't have a valid binary operator at the last index, so stop 1 before last
for (int i = startIndex; i < list.size() - 1; i++) {
if (operators.contains(list.get(i))) {
// make sure the elements before and after the operator are value types so
// that the caller can just cast them as values. If they are not, then
// this operator won't be evaluated and at the end the evaluate process, the list
// won't be reduced to just one element.
if (list.get(i - 1) instanceof ExpressionValue &
list.get(i + 1) instanceof ExpressionValue) {
return i;
}
}
}
return -1;
}
private void processUnaryOperators(List<ExpressionElement> list) throws ExpressionException {
int unaryOperatorIndex = findValidUnaryOperator(list);
while (unaryOperatorIndex >= 0) {
ExpressionOperator operator = (ExpressionOperator) list.get(unaryOperatorIndex);
ExpressionValue value = (ExpressionValue) list.get(unaryOperatorIndex + 1);
ExpressionValue newValue = value.applyUnaryOperator(operator);
list.remove(unaryOperatorIndex);
list.set(unaryOperatorIndex, newValue);
unaryOperatorIndex = findValidUnaryOperator(list);
}
}
private int findValidUnaryOperator(List<ExpressionElement> list) {
// stop 1 before end since you can't end in a valid unary operator
for (int i = 0; i < list.size() - 1; i++) {
// check any element in the list if it is a unary operator
if (list.get(i) instanceof ExpressionOperator op) {
if (!op.isUnary()) {
continue;
}
// make sure the next element is a value so the the caller can cast without fear
if (list.get(i + 1) instanceof ExpressionValue) {
return i;
}
}
}
return -1;
}
/**
* Recursively for groups (sublists surrounded by parenthesis) and process the sub list of
* elements in the group before processing the outer list. As each group is evaluated, the start
* paren operator, the end paren operator and all the tokens in between are replaced by the
* single value the group evaluated to.
* @param list the list to look for grouped sub-lists.
* @throws ExpressionException if a exception occurs processing a sub list
*/
private void processGroups(List<ExpressionElement> list) throws ExpressionException {
int groupStart = findGroupStart(list);
while (groupStart >= 0) {
int groupEndIndex = findGroupEnd(list, groupStart);
if (groupEndIndex < 0) {
throw new ExpressionException("Missing end parenthesis!");
}
ExpressionValue value = eval(list.subList(groupStart + 1, groupEndIndex));
// After evaluating, everything between the parens will be replaced by the result. So
// replace the left paren with the results and clear the next 2 entries.
list.set(groupStart, value);
list.subList(groupStart + 1, groupStart + 3).clear();
groupStart = findGroupStart(list);
}
}
private int findGroupStart(List<ExpressionElement> list) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == LEFT_PAREN) {
return i;
}
}
return -1;
}
private int findGroupEnd(List<ExpressionElement> list, int groupStart) {
int depth = 1;
for (int i = groupStart + 1; i < list.size(); i++) {
Object obj = list.get(i);
if (obj == LEFT_PAREN) {
depth++;
}
else if (obj == RIGHT_PAREN) {
if (--depth == 0) {
return i;
}
}
}
return -1;
}
// A tokenizer that keeps track of one future token. This is so the parser can handle operator
// chars that can ether be an operator by itself or part of a 2 char operator(i.e. "<", "=",
// "<=", "<<", "==")
private class LookAheadTokenizer {
private StringTokenizer tokenizer;
private String currentToken;
private String nextToken;
LookAheadTokenizer(String input) {
tokenizer = new StringTokenizer(input, TOKEN_CHARS, true);
currentToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
nextToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
}
public boolean hasMoreTokens() {
return currentToken != null;
}
public String getCurrentToken() {
return currentToken;
}
public String getNextToken() {
return nextToken;
}
public void advance(int count) {
for (int i = 0; i < count; i++) {
currentToken = nextToken;
nextToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
}
}
}
private ExpressionOperator getLastOperator(List<ExpressionElement> list) {
ExpressionElement lastElement = list.get(list.size() - 1);
return (ExpressionOperator) lastElement;
}
private boolean processSymbol(List<ExpressionElement> list, String token) {
ExpressionValue value = evaluateSymbol(token);
if (value != null) {
list.add(value);
return true;
}
return false;
}
protected ExpressionValue evaluateSymbol(String token) {
return evaluator.apply(token);
}
private boolean processOperator(List<ExpressionElement> list, String token, String nextToken) {
boolean preferBinary = shouldPreferBinaryOp(list);
ExpressionOperator op = ExpressionOperator.getOperator(token, nextToken, preferBinary);
if (op != null) {
list.add(op);
return true;
}
return false;
}
private boolean processGroupToken(List<ExpressionElement> list, String token) {
if (token.equals("(")) {
list.add(LEFT_PAREN);
return true;
}
if (token.equals(")")) {
list.add(RIGHT_PAREN);
return true;
}
return false;
}
private boolean shouldPreferBinaryOp(List<ExpressionElement> list) {
if (list.isEmpty()) {
return false;
}
ExpressionElement lastElement = list.get(list.size() - 1);
if (lastElement instanceof ExpressionValue) {
return true;
}
if (lastElement instanceof ExpressionOperator) {
return false;
}
if (lastElement == ExpressionGrouper.LEFT_PAREN) {
return false;
}
if (lastElement == ExpressionGrouper.RIGHT_PAREN) {
return true;
}
return false;
}
private boolean processNumber(List<ExpressionElement> list, String token) {
int radix = 10;
if (assumeHex && processAsHexNumber(list, token)) {
return true;
}
token = toLowerAndRemoveEndNumberDecorators(token);
if (token.startsWith("0x")) {
radix = 16;
token = token.substring(2);
}
try {
long value = (radix == 10) ? NumericUtilities.parseLong(token)
: NumericUtilities.parseHexLong(token);
list.add(new LongExpressionValue(value));
return true;
}
catch (Exception e) {
return false;
}
}
private String toLowerAndRemoveEndNumberDecorators(String token) {
token = token.toLowerCase();
if (token.endsWith("ull") || token.endsWith("llu")) {
token = token.substring(0, token.length() - 3);
}
else if (token.endsWith("ul") || token.endsWith("lu") || token.endsWith("ll")) {
token = token.substring(0, token.length() - 2);
}
else if (token.endsWith("l") || token.endsWith("u")) {
token = token.substring(0, token.length() - 1);
}
return token;
}
// parses values as a hex value (e.g. parsing "10" returns 16 instead of 10)
private boolean processAsHexNumber(List<ExpressionElement> list, String token) {
try {
long value = NumericUtilities.parseHexLong(token);
list.add(new LongExpressionValue(value));
return true;
}
catch (NumberFormatException e) {
// ignore
}
return false;
}
}

View file

@ -0,0 +1,25 @@
/* ###
* 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 generic.expressions;
/**
* Exception thrown when using an {@link ExpressionEvaluator}
*/
public class ExpressionException extends Exception {
public ExpressionException(String message) {
super(message);
}
}

View file

@ -0,0 +1,23 @@
/* ###
* 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 generic.expressions;
/**
* Grouping {@link ExpressionElement}s
*/
public enum ExpressionGrouper implements ExpressionElement {
LEFT_PAREN, RIGHT_PAREN;
}

View file

@ -0,0 +1,172 @@
/* ###
* 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 generic.expressions;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
/**
* Enum of support operators for the {@link ExpressionEvaluator}
*/
public enum ExpressionOperator implements ExpressionElement {
// unary
BITWISE_NOT("~", OpType.UNARY, 1),
LOGICAL_NOT("!", OpType.UNARY, 1),
UNARY_PLUS("+", OpType.UNARY, 1),
UNARY_MINUS("-", OpType.UNARY, 1),
// multiplicative
MULTIPLY("*", OpType.BINARY, 2),
DIVIDE("/", OpType.BINARY, 2),
// additive
ADD("+", OpType.BINARY, 3),
SUBTRACT("-", OpType.BINARY, 3),
// shift
SHIFT_LEFT("<<", OpType.BINARY, 4),
SHIFT_RIGHT(">>", OpType.BINARY, 4),
// relational
LESS_THAN("<", OpType.BINARY, 5),
GREATER_THAN(">", OpType.BINARY, 5),
LESS_THAN_OR_EQUAL("<=", OpType.BINARY, 5),
GREATER_THAN_OR_EQUAL(">=", OpType.BINARY, 5),
// equality
EQUALS("==", OpType.BINARY, 6),
NOT_EQUALS("!=", OpType.BINARY, 6),
// bitwise
BITWISE_AND("&", OpType.BINARY, 7),
BITWISE_XOR("^", OpType.BINARY, 8),
BITWISE_OR("|", OpType.BINARY, 9),
// logical
LOGICAL_AND("&&", OpType.BINARY, 10),
LOGICAL_OR("||", OpType.BINARY, 11);
public static List<Set<ExpressionOperator>> binaryOperatorsByPrecedence;
private String name;
private OpType type;
private int precedence;
private ExpressionOperator(String name, OpType type, int precedence) {
this.name = name;
this.type = type;
this.precedence = precedence;
}
@Override
public String toString() {
return name;
}
/**
* Returns a list of all the binary operators in precedence order, organized into sets where
* each set contains all the operators of the same precedence.
* @return a list of all the binary operators in precedence order, organized into sets where
* each set contains all the operators of the same precedence.
*/
public static List<Set<ExpressionOperator>> getBinaryOperatorsByPrecedence() {
if (binaryOperatorsByPrecedence == null) {
binaryOperatorsByPrecedence = buildOperatorsByPrecedenceList();
}
return binaryOperatorsByPrecedence;
}
private static List<Set<ExpressionOperator>> buildOperatorsByPrecedenceList() {
ExpressionOperator[] values = values();
LazyMap<Integer, HashSet<ExpressionOperator>> map =
LazyMap.lazyMap(new TreeMap<>(), k -> new HashSet<>());
for (ExpressionOperator op : values) {
if (op.isBinary()) {
map.get(op.precedence).add(op);
}
}
return new ArrayList<>(map.values());
}
/**
* Returns the operator for the given token and look ahead token and if we are expecting to find
* a binary operator. This method first tries merging the tokens looking for a double char
* operator first.
* @param token the first token
* @param lookahead1 the next token that may or may not be part of this operand
* @param preferBinary if we are expecting a binary operator (the previous expression element
* was an operand value). We need this to know if the token '-' is the unary operator or the
* binary operator. If the token before was an operator, then we expect a unary operator. If
* the previous was a value, then we expect a binary operator.
* @return the operator that matches the given tokens and expected type
*/
public static ExpressionOperator getOperator(String token, String lookahead1,
boolean preferBinary) {
if (lookahead1 != null) {
String doubleToken = token + lookahead1;
ExpressionOperator operator = findOperator(doubleToken, preferBinary);
if (operator != null) {
return operator;
}
}
return findOperator(token, preferBinary);
}
private static ExpressionOperator findOperator(String tokens, boolean expectBinary) {
for (ExpressionOperator operator : values()) {
if (operator.name.equals(tokens)) {
if (operator.isBinary() == expectBinary) {
return operator;
}
}
}
return null;
}
/**
* Returns the number of chars in the operator
* @return the number of chars in the operator
*/
public int size() {
return name.length();
}
/**
* Returns if the operator is a unary operator.
* @return if the operator is a unary operator.
*/
public boolean isUnary() {
return type == OpType.UNARY;
}
/**
* Returns if the operator is a binary operator.
* @return if the operator is a binary operator.
*/
public boolean isBinary() {
return type == OpType.BINARY;
}
private enum OpType {
UNARY, BINARY
}
}

View file

@ -0,0 +1,42 @@
/* ###
* 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 generic.expressions;
/**
* Operand types use by the {@link ExpressionEvaluator} must implement this interface.
*/
public interface ExpressionValue extends ExpressionElement {
/**
* Method called to apply a unary operator to this value.
* @param operator the operator being applied
* @return the new value after the operator is applied to this value
* @throws ExpressionException if the operator is not applicable for this value
*/
public ExpressionValue applyUnaryOperator(ExpressionOperator operator) throws ExpressionException;
/**
* Method called to apply a binary operator to this value.
* @param operator the binary operator being applied.
* @param value the other value to combine with this value by the operator
* @return the new value after the operator is applied to this value
* @throws ExpressionException if the operator is not applicable for this value or the the other
* value is not applicable for this operand and operator
*/
public ExpressionValue applyBinaryOperator(ExpressionOperator operator, ExpressionValue value)
throws ExpressionException;
}

View file

@ -0,0 +1,110 @@
/* ###
* 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 generic.expressions;
/**
* Long operand values. See {@link ExpressionValue}. Defines supported operators and other
* operands for expression values that are long values.
*/
public class LongExpressionValue implements ExpressionValue {
private final long value;
public LongExpressionValue(long value) {
this.value = value;
}
public long getLongValue() {
return value;
}
@Override
public String toString() {
return Long.toString(value);
}
@Override
public ExpressionValue applyUnaryOperator(ExpressionOperator operator) throws ExpressionException {
switch (operator) {
case BITWISE_NOT:
return new LongExpressionValue(~value);
case LOGICAL_NOT:
return new LongExpressionValue(value == 0 ? 1 : 0);
case UNARY_MINUS:
return new LongExpressionValue(-value);
case UNARY_PLUS:
return this;
default:
throw new ExpressionException(
"Unary Operator " + operator + " not supported by Long values!");
}
}
@Override
public ExpressionValue applyBinaryOperator(ExpressionOperator operator, ExpressionValue operand)
throws ExpressionException {
if (!(operand instanceof LongExpressionValue longOperand)) {
throw new ExpressionException("Unsupported operand type for Long: " + value);
}
long otherValue = longOperand.value;
switch (operator) {
case BITWISE_AND:
return new LongExpressionValue(value & otherValue);
case BITWISE_OR:
return new LongExpressionValue(value | otherValue);
case BITWISE_XOR:
return new LongExpressionValue(value ^ otherValue);
case DIVIDE:
return new LongExpressionValue(value / otherValue);
case EQUALS:
return new LongExpressionValue(value == otherValue ? 1 : 0);
case GREATER_THAN:
return new LongExpressionValue(value > otherValue ? 1 : 0);
case GREATER_THAN_OR_EQUAL:
return new LongExpressionValue(value >= otherValue ? 1 : 0);
case SHIFT_LEFT:
return new LongExpressionValue(value << otherValue);
case LESS_THAN:
return new LongExpressionValue(value < otherValue ? 1 : 0);
case LESS_THAN_OR_EQUAL:
return new LongExpressionValue(value <= otherValue ? 1 : 0);
case LOGICAL_AND:
int b1 = value == 0 ? 0 : 1;
int b2 = otherValue == 0 ? 0 : 1;
return new LongExpressionValue(b1 & b2);
case LOGICAL_OR:
b1 = value == 0 ? 0 : 1;
b2 = otherValue == 0 ? 0 : 1;
return new LongExpressionValue(b1 | b2);
case SUBTRACT:
return new LongExpressionValue(value - otherValue);
case NOT_EQUALS:
return new LongExpressionValue(value == otherValue ? 0 : 1);
case ADD:
return new LongExpressionValue(value + otherValue);
case SHIFT_RIGHT:
return new LongExpressionValue(value >> otherValue);
case MULTIPLY:
return new LongExpressionValue(value * otherValue);
default:
throw new ExpressionException(
"Binary Operator \"" + operator + "\" not supported by Long values!");
}
}
}

View file

@ -0,0 +1,206 @@
/* ###
* 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 generic.expressions;
import static org.junit.Assert.*;
import org.junit.Test;
public class ExpressionEvaluatorTest {
@Test
public void testUnaryPlus() {
assertEval(100, "+100");
}
@Test
public void testUnaryMinus() {
assertEval(-100, "-100");
}
@Test
public void testAdd() {
assertEval(100, "20+10 + 70");
}
@Test
public void testSubtract() {
assertEval(5, "20-10-5");
assertEval(5, "20-5-10");
}
@Test
public void testMultiply() {
assertEval(42, "2*3*7");
}
@Test
public void testDivide() {
assertEval(1, "8/4/2");
assertEval(1, "5/4");
}
@Test
public void testShiftLeft() {
assertEval(4, "16>>2");
}
@Test
public void testShiftRight() {
assertEval(8, "1<<3");
}
@Test
public void testBitWiseNot() {
assertEval(-1, "~0");
}
@Test
public void testLogicalNot() {
assertEval(0, "!1");
assertEval(1, "!0");
assertEval(0, "!124");
}
@Test
public void testGreaterThan() {
assertEval(1, "8>5");
assertEval(0, "5>8");
assertEval(0, "8>8");
}
@Test
public void testLessThan() {
assertEval(0, "8<5");
assertEval(1, "5<8");
assertEval(0, "8<8");
}
@Test
public void testGreaterThanOrEqual() {
assertEval(1, "8>=5");
assertEval(0, "5>=8");
assertEval(1, "8>=8");
}
@Test
public void testLessThanOrEqual() {
assertEval(0, "8<=5");
assertEval(1, "5<=8");
assertEval(1, "8<=8");
}
@Test
public void testAddSubtractAssociatesLeftToRight() {
assertEval(110, "100+30-10-10");
assertEval(110, "100-10-10+30");
}
@Test
public void testMultiplyDivide() {
assertEval(100, "10*30/3");
assertEval(90, "10/3*30");
}
@Test
public void testBitwiseAnd() {
assertEval(0x4, "0xffff & 0x4");
assertEval(0x0, "0x4 & 0x2");
}
@Test
public void testBitwiseOr() {
assertEval(0x6, "0x2 | 0x4");
}
@Test
public void testBitwiseXor() {
assertEval(0x2, "0x3 ^ 0x1");
}
@Test
public void testLogicalAnd() {
assertEval(1, "0xffff && 0x4");
assertEval(0, "0x4 && 0");
}
@Test
public void testLogicalOr() {
assertEval(1, "0x2 || 0x4");
assertEval(0, "0 || 0");
}
@Test
public void testMixedPrecedence() {
assertEval(23, "10+3*5-8/4");
}
@Test
public void testGrouping() {
assertEval(42, "6*(3+4)");
assertEval(16, "1 << (8/2)");
assertEval(-1, "~(-1+1)");
assertEval(13, "1+(3 * (7+1)/2)");
}
@Test
public void testStackedUnaryOperators() {
assertEval(-1, "~~~0");
}
@Test
public void testInvalidSyntax() {
assertEvalNull("5 5");
assertEvalNull("+");
assertEvalNull("<< 5");
assertEvalNull("5 +");
assertEvalNull("(3+2");
}
@Test
public void testMixedValues() {
assertEval(26, "10+0x10");
}
@Test
public void testHexOnly() {
assertEvalHexOnly(22, "10+6");
}
private void assertEval(long expected, String expression) {
long result = ExpressionEvaluator.evaluateToLong(expression);
assertEquals(expected, result);
}
private void assertEvalHexOnly(long expected, String expression) {
ExpressionEvaluator evaluator = new ExpressionEvaluator(true);
long result;
try {
result = evaluator.parseAsLong(expression);
assertEquals(expected, result);
}
catch (ExpressionException e) {
// ignore
}
}
private void assertEvalNull(String expression) {
assertNull(ExpressionEvaluator.evaluateToLong(expression));
}
}

View file

@ -15,656 +15,270 @@
*/
package ghidra.program.util;
import java.util.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.List;
import generic.expressions.*;
import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.SymbolPath;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.NumericUtilities;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
/**
* The <CODE>AddressEvaluator</CODE> class provides a way to evaluate a string
* that represents an address and resolve it to an address for a particular program.
* Class for evaluating expressions as an Address. See
* {@link ExpressionOperator} for the full list of supported operators. All values are interpreted
* as longs or symbols that resolve to an address.
* <P>
* ExpressionEvaluators can operate in either decimal or hex mode. If in hex mode, all numbers are
* assumed to be hexadecimal values. In decimal mode, numbers are assumed to be decimal values, but
* hexadecimal values can still be specified by prefixing them with "0x".
* <P>
* There are also two convenience static methods that can be called to evaluate address expressions.
* These methods will either return an Address as the result or null if there was an error
* evaluating the expression. To get error messages related to parsing the expression, instantiate
* an AddressEvaluator and call {@link #parseAsAddress(String)} which will throw a
* {@link ExpressionException} when the expression can't be evaluated.
*/
public class AddressEvaluator {
public class AddressEvaluator extends ExpressionEvaluator {
private static final String TOKEN_CHARS = "+-*/()<>|^&~ =";
private Reference<Program> programReference;
private AddressFactory addressFactory;
private AddressSpace preferredSpace;
/**
* Gets a legitimate address for the specified program as indicated by the string.
* Gets a valid address for the specified program as indicated by the input expression.
* @param p the program to use for determining the address.
* @param baseAddr the base address to use for relative addressing.
* @param s string representation of the address desired.
* @param inputExpression string representation of the address desired.
* @return the address. Otherwise, return null if the string fails to evaluate
* to a unique legitimate address.
*/
public static Address evaluate(Program p, Address baseAddr, String s) {
AddressFactory af = p.getAddressFactory();
SymbolTable st = p.getSymbolTable();
List<Object> list = new ArrayList<Object>();
if (baseAddr != null) {
list.add(baseAddr);
}
if (!parseToList(s, af, st, list)) {
return null;
}
Object obj = eval(list);
if (obj instanceof Address) {
return (Address) obj;
}
else if (obj instanceof Long) {
try {
return af.getDefaultAddressSpace().getAddress(((Long) obj).longValue(), true);
}
catch (Exception e) {
// ignore
}
}
return null;
}
public static Long evaluateToLong(String s) {
List<Object> list = new ArrayList<Object>();
if (!parseToList(s, null, null, list)) {
return null;
}
Object obj = eval(list);
if (obj instanceof Address) {
return ((Address) obj).getOffset();
}
else if (obj instanceof Long) {
return (Long) obj;
}
return null;
}
protected static boolean parseToList(String s, AddressFactory af, SymbolTable st,
List<Object> list) {
StringTokenizer parser = new StringTokenizer(s, TOKEN_CHARS, true);
String lookahead = null;
while (lookahead != null || parser.hasMoreTokens()) {
String tok = null;
if (lookahead != null) {
tok = lookahead;
lookahead = null;
}
else {
tok = parser.nextToken();
}
if (tok.equals(" ")) {
continue;
}
// = must be followed by =, others can be followed
if ("=!<>|&".contains(tok)) {
lookahead = parser.nextToken();
tok = checkDoubleToken(tok, lookahead);
// if tok is now longer, consumed lookahead
if (tok.length() > 1) {
lookahead = null;
}
}
Object obj = Operator.getOperator(tok);
if (obj == null) {
obj = getValueObject(st, af, tok);
}
if (obj == null) {
return false;
}
list.add(obj);
}
return true;
}
private static String checkDoubleToken(String tok, String lookahead) {
switch (tok) {
case "=":
if (lookahead.equals("=")) {
return "==";
}
break;
case "<":
if (lookahead.equals("=")) {
return "<=";
}
if (lookahead.equals("<")) {
return "<<";
}
break;
case ">":
if (lookahead.equals("=")) {
return ">=";
}
if (lookahead.equals(">")) {
return ">>";
}
break;
case "!":
if (lookahead.equals("=")) {
return "!=";
}
break;
case "|":
if (lookahead.equals("|")) {
return "||";
}
break;
case "&":
if (lookahead.equals("&")) {
return "&&";
}
break;
}
return tok;
public static Address evaluate(Program p, String inputExpression) {
return evaluate(p, null, inputExpression);
}
/**
* Gets a legitimate address for the specified program as indicated by the string.
* Gets a valid address for the specified program as indicated by the input expression.
* @param p the program to use for determining the address.
* @param s string representation of the address desired.
* @param baseAddr the base address to use for relative addressing.
* @param inputExpression string representation of the address desired.
* @return the address. Otherwise, return null if the string fails to evaluate
* to a legitimate address.
* to a unique legitimate address.
*/
public static Address evaluate(Program p, String s) {
return evaluate(p, null, s);
public static Address evaluate(Program p, Address baseAddr, String inputExpression) {
AddressEvaluator evaluator = new AddressEvaluator(p, true);
try {
return evaluator.parseAsAddress(inputExpression);
}
catch (ExpressionException e) {
return null;
}
}
/**
* Utility method for creating an Address object from a byte array. The Address object may or may not
* be a legitimate Address in the program's address space. This method is meant to provide a way of
* creating an Address object from a sequence of bytes that can be used for additional tests and
* comparisons.
*
* @param p - program being analyzed.
* @param addrBytes - byte array to use containing the values the address will be constructed from.
* @return - Address object constructed from the addrBytes array. Returns null if the program is null,
* addrBytes is null, or the length of addrBytes does not match the default Pointer size or does not contain
* a valid offset.
*
* Constructs an AddressEvalutor for the given program and in the specified hex/decimal mode.
* @param program the program to use to evaluate expressions into valid addresses.
* @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers.
*/
public static Address evaluate(Program p, byte[] addrBytes) {
boolean isBigEndian = p.getMemory().isBigEndian();
int ptrSize = p.getDefaultPointerSize();
int index = 0;
long offset = 0;
// Make sure correct # of bytes were passed
if (addrBytes == null || addrBytes.length != ptrSize) {
return null;
}
/*
* Make sure we account for endianness of the program.
* Computing the number of bits to shift the current byte value
* is different for Little vs. Big Endian. Need to multiply by
* 8 to shift in 1-byte increments.
*/
if (isBigEndian) {
index = 0;
while (index < addrBytes.length) {
offset += (addrBytes[index] & 0xff) << ((addrBytes.length - index - 1) * 8);
index++;
}
}
else {
// Program is LittleEndian
index = addrBytes.length - 1;
while (index >= 0) {
offset += ((addrBytes[index] & 0xff) << (index * 8));
index--;
}
}
AddressSpace space = p.getAddressFactory().getDefaultAddressSpace();
try {
return space.getAddress(offset, true);
}
catch (AddressOutOfBoundsException e) {
return null;
}
public AddressEvaluator(Program program, boolean assumeHex) {
this(program, null, assumeHex);
}
private static Object getValueObject(SymbolTable st, AddressFactory af, String tok) {
/**
* Constructs an AdddressEvaluator without a full program. This version will not be able to
* evaluate symbol or memory block names. This is mostly for backwards compatibility.
* @param factory the address factory for creating addresses
* @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers.
*/
public AddressEvaluator(AddressFactory factory, boolean assumeHex) {
this(factory, null, assumeHex);
}
if (st == null || af == null) {
return getValueObject(tok);
}
/**
* Constructs an AddressEvalutor for the given program and in the specified hex/decimal mode.
* @param program the program to use to evaluate expressions into valid addresses.
* @param defaultSpace The address space to use when converting long values into addresses. If
* this value is null, then the default address space will be used.
* @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers.
*/
public AddressEvaluator(Program program, AddressSpace defaultSpace, boolean assumeHex) {
this(program.getAddressFactory(), defaultSpace, assumeHex);
this.programReference = new WeakReference<>(program);
}
try {
return NumericUtilities.parseHexLong(tok);
private AddressEvaluator(AddressFactory factory, AddressSpace defaultSpace, boolean assumeHex) {
super(assumeHex);
this.addressFactory = factory;
this.preferredSpace = defaultSpace;
}
/**
* Evaluates the given input expression as an address.
* @param input the expression to evaluate
* @return the Address the expression evaluates to
* @throws ExpressionException if the input expression can't be evaluated to a valid, unique
* address.
*/
public Address parseAsAddress(String input) throws ExpressionException {
return this.parseAsRelativeAddress(input, null);
}
/**
* Evaluates the given input expression as a relative offset that will be added to the given
* base address.
* @param input the expression to evaluate as an offset
* @param baseAddress the base address the evaluted expression will be added to to get the
* resulting address.
* @return the Address after the evaluated offset is added to the given base address.
* @throws ExpressionException if the input expression can't be evaluated to a valid, unique
* address.
*/
public Address parseAsRelativeAddress(String input, Address baseAddress)
throws ExpressionException {
ExpressionValue expressionValue = baseAddress == null ? parse(input)
: parse(input, new AddressExpressionValue(baseAddress));
if (expressionValue instanceof AddressExpressionValue addressValue) {
return validateAddressSpace(addressValue.getAddress());
}
catch (NumberFormatException e) {
// ignore
if (expressionValue instanceof LongExpressionValue longValue) {
long offset = longValue.getLongValue();
AddressSpace space = getAddressSpace();
try {
return space.getAddressInThisSpaceOnly(offset);
}
catch (AddressOutOfBoundsException e) {
throw new ExpressionException(e.getMessage());
}
}
Address address = af.getAddress(tok);
if (address != null) {
throw new ExpressionException("Expression did not evalute to a long! Got a " +
expressionValue.getClass() + " instead.");
}
/**
* Returns the {@link AddressFactory} being used by this address evaluator
* @return the {@link AddressFactory} being used by this address evaluator
*/
public AddressFactory getAddressFactory() {
return addressFactory;
}
/**
* Sets the {@link AddressSpace} to be used to convert long values into addresses.
* @param space the address space to convert long values into addresses
*/
public void setPreferredAddressSpace(AddressSpace space) {
this.preferredSpace = space;
}
// checks if the given address's address space is compatible with the preferred address space
private Address validateAddressSpace(Address address) throws ExpressionException {
if (preferredSpace == null) {
return address;
}
List<Symbol> globalSymbols = st.getLabelOrFunctionSymbols(tok, null);
if (globalSymbols.size() == 1) {
return globalSymbols.get(0).getAddress();
AddressSpace space = address.getAddressSpace();
if (space.equals(preferredSpace)) {
return address;
}
return getValueObject(tok);
if (isOverlayRelated(space, preferredSpace)) {
return preferredSpace.getAddress(address.getOffset());
}
throw new ExpressionException("Selected address space is not compatible with expression!");
}
private static Object getValueObject(String strValue) {
try {
int start = 0;
int radix = 10;
if (strValue.indexOf("0x") == 0) {
start = 2;
radix = 16;
}
strValue = strValue.toLowerCase();
if (strValue.endsWith("ull") || strValue.endsWith("llu")) {
strValue = strValue.substring(start, strValue.length() - 3);
}
else if (strValue.endsWith("ul") || strValue.endsWith("lu") || strValue.endsWith("ll")) {
strValue = strValue.substring(start, strValue.length() - 2);
}
else if (strValue.endsWith("l") || strValue.endsWith("u")) {
strValue = strValue.substring(start, strValue.length() - 1);
}
else {
strValue = strValue.substring(start);
}
return (radix == 10) ? NumericUtilities.parseLong(strValue)
: NumericUtilities.parseHexLong(strValue);
}
catch (RuntimeException e) {
// ignore
}
return null;
private boolean isOverlayRelated(AddressSpace space1, AddressSpace space2) {
AddressSpace base1 = getBaseSpace(space1);
AddressSpace base2 = getBaseSpace(space2);
return base1.equals(base2);
}
private static Object eval(List<Object> list) {
// first evaluate any grouped expressions
boolean done = false;
while (!done) {
done = true;
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == Operator.LEFT_PAREN) {
done = false;
int end = findMatchingParen(list, i);
if (end < 0) {
return null;
}
Object obj = eval(list.subList(i + 1, end));
if (obj == null) {
return null;
}
list.subList(i, i + 2).clear();
list.set(i, obj);
}
}
private AddressSpace getBaseSpace(AddressSpace space) {
if (space instanceof OverlayAddressSpace overlaySpace) {
return overlaySpace.getOverlayedSpace();
}
//check for leading Minus
if (list.size() > 1 && list.get(0) == Operator.MINUS) {
Object obj = list.get(1);
if (obj instanceof Long) {
obj = -((Long) obj).longValue();
list.remove(0);
list.set(0, obj);
}
}
//check for leading ~
if (list.size() > 1 && list.get(0) == Operator.NOT) {
Object obj = list.get(1);
if (obj instanceof Long) {
obj = ~((Long) obj).longValue();
list.remove(0);
list.set(0, obj);
}
}
//check for trailing leading ~
if (list.size() > 3 && list.get(2) == Operator.NOT) {
Object obj = list.get(3);
if (obj instanceof Long) {
obj = ~((Long) obj).longValue();
list.remove(2);
list.set(2, obj);
}
}
// evaluate all SHIFT because they have precedence
if (!evaluateOperator(list, Operator.RIGHTSHIFT, Operator.LEFTSHIFT)) {
return null;
}
// evaluate all TIMES because they have precedence
if (!evaluateOperator(list, Operator.TIMES, Operator.DIVIDE)) {
return null;
}
// evaluate Plus and Minus, same precedence, but do plus then minus
if (!evaluateOperator(list, Operator.PLUS, Operator.MINUS)) {
return null;
}
// evaluate & ^ |
if (!evaluateOperator(list, Operator.AND, null)) {
return null;
}
if (!evaluateOperator(list, Operator.XOR, null)) {
return null;
}
if (!evaluateOperator(list, Operator.OR, null)) {
return null;
}
if (!evaluateOperator(list, Operator.EQUALS, Operator.NOTEQUALS)) {
return null;
}
if (!evaluateOperator(list, Operator.LESS, Operator.GREATER)) {
return null;
}
if (!evaluateOperator(list, Operator.LESSEQUALS, Operator.GREATEREQUALS)) {
return null;
}
if (!evaluateOperator(list, Operator.LOG_AND, null)) {
return null;
}
if (!evaluateOperator(list, Operator.LOG_OR, null)) {
return null;
}
if (list.size() != 1) {
return null;
}
return list.get(0);
return space;
}
private static boolean evaluateOperator(List<Object> list, Operator op1, Operator op2) {
boolean done;
done = false;
while (!done) {
done = true;
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
if (obj == op1 || obj == op2) {
done = false;
if (i == 0 || i == list.size() - 1) {
return false;
}
Object value = computeValue(list.get(i - 1), (Operator) obj, list.get(i + 1));
if (value == null) {
return false;
}
list.subList(i, i + 2).clear();
list.set(i - 1, value);
}
}
private AddressSpace getAddressSpace() {
if (preferredSpace != null) {
return preferredSpace;
}
return true;
}
private static Object computeValue(Object v1, Operator op, Object v2) {
if (op == Operator.TIMES) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() * ((Long) v2).longValue();
}
}
if (op == Operator.DIVIDE) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() / ((Long) v2).longValue();
}
}
else if (op == Operator.AND) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() & ((Long) v2).longValue();
}
}
else if (op == Operator.XOR) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() ^ ((Long) v2).longValue();
}
}
else if (op == Operator.OR) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() | ((Long) v2).longValue();
}
}
else if (op == Operator.LEFTSHIFT) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() << ((Long) v2).longValue();
}
}
else if (op == Operator.RIGHTSHIFT) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() >> ((Long) v2).longValue();
}
}
else if (op == Operator.PLUS) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() + ((Long) v2).longValue();
}
else if ((v1 instanceof Address) && (v2 instanceof Long)) {
return ((Address) v1).addWrap(((Long) v2).longValue());
}
else if ((v1 instanceof Long) && (v2 instanceof Address)) {
return ((Address) v2).addWrap(((Long) v1).longValue());
}
}
else if (op == Operator.NOT) {
if (v2 instanceof Long) {
return ~(((Long) v2).longValue());
}
else if (v2 instanceof Address) {
return ((Address) v2).getNewAddress(~(((Long) v2).longValue()));
}
}
else if (op == Operator.MINUS) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() - ((Long) v2).longValue();
}
else if ((v1 instanceof Address) && (v2 instanceof Long)) {
return ((Address) v1).subtractWrap(((Long) v2).longValue());
}
else if ((v1 instanceof Address) && (v2 instanceof Address)) {
return ((Address) v1).subtract((Address) v2);
}
}
else if (op == Operator.EQUALS) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff == 0L ? 1L : 0L;
}
}
else if (op == Operator.NOTEQUALS) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff != 0L ? 1L : 0L;
}
}
else if (op == Operator.LESSEQUALS) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff <= 0L ? 1L : 0L;
}
}
else if (op == Operator.GREATEREQUALS) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff >= 0L ? 1L : 0L;
}
}
else if (op == Operator.LESS) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff < 0L ? 1L : 0L;
}
}
else if (op == Operator.GREATER) {
Long diff = getDifference(v1, v2);
if (diff != null) {
return diff > 0L ? 1L : 0L;
}
}
else if (op == Operator.LOG_AND) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
boolean test = (((Long) v1).longValue()) != 0 && (((Long) v2).longValue()) != 0;
return test ? 1L : 0L;
}
}
else if (op == Operator.LOG_OR) {
if ((v1 instanceof Long) && (v2 instanceof Long)) {
boolean test = (((Long) v1).longValue()) != 0 || (((Long) v2).longValue()) != 0;
return test ? 1L : 0L;
}
}
return null;
}
private static Long getDifference(Object v1, Object v2) {
if ((v1 instanceof Address) && (v2 instanceof Long)) {
return ((Address) v1).subtractWrap(((Long) v2).longValue()).getOffset();
}
else if ((v1 instanceof Address) && (v2 instanceof Address)) {
return ((Address) v1).subtract((Address) v2);
}
else if ((v1 instanceof Long) && (v2 instanceof Long)) {
return ((Long) v1).longValue() - ((Long) v2).longValue();
}
return null;
}
private static int findMatchingParen(List<Object> list, int index) {
int depth = 1;
for (int j = index + 1; j < list.size(); j++) {
Object obj = list.get(j);
if (obj == Operator.LEFT_PAREN) {
depth++;
}
else if (obj == Operator.RIGHT_PAREN) {
if (--depth == 0) {
return j;
}
}
}
return -1;
}
}
class Operator {
static Operator PLUS = new Operator("+");
static Operator MINUS = new Operator("-");
static Operator TIMES = new Operator("*");
static Operator DIVIDE = new Operator("/");
static Operator AND = new Operator("&");
static Operator OR = new Operator("|");
static Operator NOT = new Operator("~");
static Operator XOR = new Operator("^");
static Operator LEFTSHIFT = new Operator("<<");
static Operator RIGHTSHIFT = new Operator(">>");
static Operator LEFT_PAREN = new Operator("(");
static Operator RIGHT_PAREN = new Operator(")");
static Operator LOG_OR = new Operator("||");
static Operator LOG_AND = new Operator("&&");
static Operator EQUALS = new Operator("==");
static Operator NOTEQUALS = new Operator("!=");
static Operator LESS = new Operator("<");
static Operator GREATER = new Operator(">");
static Operator LESSEQUALS = new Operator("<=");
static Operator GREATEREQUALS = new Operator(">=");
final String name;
private Operator(String name) {
this.name = name;
return addressFactory.getDefaultAddressSpace();
}
@Override
public String toString() {
return name;
protected ExpressionValue evaluateSymbol(String input) {
Address address = addressFactory.getAddress(input);
if (address != null) {
return new AddressExpressionValue(address);
}
Program program = getProgram();
if (program != null) {
return getAddressForProgram(program, input);
}
return null;
}
/**
* Gets the static object implementation of an operator.
* @param tok token string for the operator
* @return the static operator object.
*/
public static Operator getOperator(String tok) {
if (tok.equals("+")) {
return PLUS;
private ExpressionValue getAddressForProgram(Program program, String input) {
Address address = getAddressForSymbol(program, input);
if (address == null) {
address = getAddressFromMemoryMap(program, input);
}
if (tok.equals("&")) {
return AND;
return address == null ? null : new AddressExpressionValue(address);
}
private Address getAddressFromMemoryMap(Program program, String input) {
Memory memory = program.getMemory();
MemoryBlock block = memory.getBlock(input);
if (block != null) {
return block.getStart();
}
if (tok.equals("|")) {
return OR;
return null;
}
private Address getAddressForSymbol(Program program, String input) {
SymbolPath symbolPath = new SymbolPath(input);
String symbolName = symbolPath.getName();
SymbolPath parent = symbolPath.getParent();
Namespace namespace = null;
if (parent != null) {
namespace = getParentNamespace(program, parent);
if (namespace == null) {
// there was a namespace specified, but not uniquely found, so can't resolve.
return null;
}
}
if (tok.equals("^")) {
return XOR;
SymbolTable symbolTable = program.getSymbolTable();
List<Symbol> symbols = symbolTable.getLabelOrFunctionSymbols(symbolName, namespace);
if (symbols.size() == 1) {
return symbols.get(0).getAddress();
}
else if (tok.equals("-")) {
return MINUS;
return null;
}
private Namespace getParentNamespace(Program program, SymbolPath path) {
if (path == null) {
return null;
}
else if (tok.equals("~")) {
return NOT;
List<Namespace> spaces = NamespaceUtils.getNamespaceByPath(program, null, path.getPath());
if (spaces.size() == 1) {
return spaces.get(0);
}
else if (tok.equals("*")) {
return TIMES;
}
else if (tok.equals("/")) {
return DIVIDE;
}
else if (tok.equals(")")) {
return RIGHT_PAREN;
}
else if (tok.equals("(")) {
return LEFT_PAREN;
}
else if (tok.equals("<<")) {
return LEFTSHIFT;
}
else if (tok.equals(">>")) {
return RIGHTSHIFT;
}
else if (tok.equals("==")) {
return EQUALS;
}
else if (tok.equals("!=")) {
return NOTEQUALS;
}
else if (tok.equals("<")) {
return LESS;
}
else if (tok.equals(">")) {
return GREATER;
}
else if (tok.equals("<=")) {
return LESSEQUALS;
}
else if (tok.equals(">=")) {
return GREATEREQUALS;
}
else if (tok.equals("||")) {
return LOG_OR;
}
else if (tok.equals("&&")) {
return LOG_AND;
return null;
}
private Program getProgram() {
if (programReference != null) {
return programReference.get();
}
return null;
}

View file

@ -0,0 +1,167 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.program.util;
import generic.expressions.*;
import ghidra.program.model.address.Address;
/**
* Address operand values. See {@link ExpressionValue}. Defines supported operators and other
* operands for expression values that are addresses.
*/
public class AddressExpressionValue implements ExpressionValue {
private Address value;
public AddressExpressionValue(Address address) {
this.value = address;
}
@Override
public ExpressionValue applyUnaryOperator(ExpressionOperator operator) throws ExpressionException {
long offset = value.getOffset();
switch (operator) {
case BITWISE_NOT:
return addressExpressionOf(~offset);
case UNARY_MINUS:
return addressExpressionOf(-offset);
case UNARY_PLUS:
return this;
default:
throw new ExpressionException(
"Unary Operator " + operator + " not supported by Long values!");
}
}
private AddressExpressionValue addressExpressionOf(long offset) {
Address address = value.getNewAddress(offset);
return new AddressExpressionValue(addressOf(offset));
}
private AddressExpressionValue addressExpressionOf(Address address) {
return new AddressExpressionValue(address);
}
private Address addressOf(long offset) {
return value.getNewAddress(offset);
}
@Override
public ExpressionValue applyBinaryOperator(ExpressionOperator operator, ExpressionValue operand)
throws ExpressionException {
if (operand instanceof LongExpressionValue longOperand) {
return applyBinaryOperator(operator, longOperand);
}
if (operand instanceof AddressExpressionValue addressOperand) {
return applyBinaryOperator(operator, addressOperand);
}
throw new ExpressionException("Unsupported operand type for Long: " + value);
}
private ExpressionValue applyBinaryOperator(ExpressionOperator operator,
LongExpressionValue expressionValue) throws ExpressionException {
long otherValue = expressionValue.getLongValue();
long offset = value.getOffset();
int compareResult = Long.compareUnsigned(offset, otherValue);
switch (operator) {
case BITWISE_AND:
return addressExpressionOf(offset & otherValue);
case BITWISE_OR:
return addressExpressionOf(offset | otherValue);
case BITWISE_XOR:
return addressExpressionOf(offset ^ otherValue);
case DIVIDE:
return addressExpressionOf(offset / otherValue);
case SUBTRACT:
return addressExpressionOf(value.subtract(otherValue));
case ADD:
return addressExpressionOf(value.add(otherValue));
case MULTIPLY:
return addressExpressionOf(offset * otherValue);
case SHIFT_LEFT:
return addressExpressionOf(offset << otherValue);
case SHIFT_RIGHT:
return addressExpressionOf(offset >> otherValue);
case EQUALS:
return booleanExpression(compareResult == 0);
case GREATER_THAN:
return booleanExpression(compareResult > 0);
case LESS_THAN:
return booleanExpression(compareResult < 0);
case GREATER_THAN_OR_EQUAL:
return booleanExpression(compareResult >= 0);
case LESS_THAN_OR_EQUAL:
return booleanExpression(compareResult <= 0);
default:
throw new ExpressionException(
"Binary Operator \"" + operator +
"\" with Long operands not supported by Address values!");
}
}
private ExpressionValue booleanExpression(boolean b) {
return new LongExpressionValue(b ? 1 : 0);
}
private ExpressionValue applyBinaryOperator(ExpressionOperator operator,
AddressExpressionValue expressionValue) throws ExpressionException {
Address otherValue = expressionValue.getAddress();
long otherValueOffset = otherValue.getOffset();
long offset = value.getOffset();
int compareResult = value.compareTo(otherValue);
switch (operator) {
case BITWISE_AND:
return new LongExpressionValue(offset & otherValueOffset);
case BITWISE_OR:
return new LongExpressionValue(offset | otherValueOffset);
case BITWISE_XOR:
return new LongExpressionValue(offset ^ otherValueOffset);
case SUBTRACT:
return new LongExpressionValue(value.subtract(otherValue));
case ADD:
return new LongExpressionValue(offset + otherValueOffset);
case EQUALS:
return booleanExpression(compareResult == 0);
case GREATER_THAN:
return booleanExpression(compareResult > 0);
case LESS_THAN:
return booleanExpression(compareResult < 0);
case GREATER_THAN_OR_EQUAL:
return booleanExpression(compareResult >= 0);
case LESS_THAN_OR_EQUAL:
return booleanExpression(compareResult <= 0);
default:
throw new ExpressionException(
"Binary Operator \"" + operator +
"\" with Long operands not supported by Address values!");
}
}
public Address getAddress() {
return value;
}
@Override
public String toString() {
return value.toString();
}
}

View file

@ -27,6 +27,7 @@ import java.util.Iterator;
import org.bouncycastle.util.Arrays;
import generic.expressions.ExpressionEvaluator;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.script.GhidraScript;
@ -35,7 +36,6 @@ import ghidra.app.util.cparser.C.CParserUtils.CParseResults;
import ghidra.app.util.cparser.CPP.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.util.AddressEvaluator;
import ghidra.util.Msg;
public class CreateAVR8GDTArchiveScript extends GhidraScript {
@ -230,7 +230,7 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript {
continue;
}
lvalue = AddressEvaluator.evaluateToLong(expandValue);
lvalue = ExpressionEvaluator.evaluateToLong(expandValue);
if (lvalue == null) {
continue;
}

View file

@ -140,7 +140,7 @@ public class MemoryMapPluginScreenShots extends GhidraScreenShotGenerator {
DialogComponentProvider dialog = getDialog();
GhidraComboBox<?> comboBox = (GhidraComboBox<?>) getInstanceField("comboBox", dialog);
selectItem(comboBox, "Byte Mapped");
runSwing(() -> dialog.setStatusText(""));
captureDialog();
drawRectangleAround(comboBox, Palette.GREEN, 10);