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/MoveMemory.png||GHIDRA||||END|
src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.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/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/Appendix.htm||GHIDRA||||END|
src/main/help/help/topics/Misc/Welcome_to_Ghidra_Help.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| src/main/help/help/topics/Navigation/Navigation.htm||GHIDRA||||END|

View file

@ -196,6 +196,8 @@
<!-- Formerly The Byte Viewer --> <!-- Formerly The Byte Viewer -->
<tocdef id="Program Annotation" text="Program Annotation" target="help/topics/ProgramManagerPlugin/Program_Annotation.htm"> <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="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="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" /> <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>Select the <B>User</B> radio button.</LI>
<LI>Enter an address, or click in the Code Browser at the address of the new <LI>Enter an address
fallthrough.</LI> (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; <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> 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>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 <P><B><I>Start Address</I></B> - Enter the start address
program language defines multiple address spaces, the address space must also be (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. 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 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 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>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> 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 <P><B><I>New End Address -&nbsp;</I></B> Enter the new ending address
NEW starting address will be computed.</P> (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 <P><I><IMG src="help/shared/note.png" border="0"> You cannot move a block under the
following conditions:</I></P> following conditions:</I></P>
@ -404,11 +409,15 @@
enter the split point:</P> enter the split point:</P>
<UL> <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 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> <LI>Enter a length for the second block (new block).</LI>
</UL> </UL>
@ -482,7 +491,9 @@
<BLOCKQUOTE> <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> be before the current start address.</P>
<P><I><B>End Address -</B></I> Displays the end address of the block (not editable).</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 <P><I><B>Start Address -</B></I> Displays the start address of the block (not
editable).</P> 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> the current end address.</P>
<P><I><B>Block Length -</B></I> Displays the length of the block.&nbsp; A new value can be <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>
<LI>The "Enter Base Address" field in the dialog is filled in with the first address in the <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> <LI>
The "Select Data Size" combo box has an entry for sizes 1, 2, 4, and 8. The size you select 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"> <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 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 and specifies the reference destination as a memory offset within a selected address
space. &nbsp;The address offset entry is always space. Enter an address
interpretted as a unsigned hex value &nbsp;(i.e., the "0x" entry prefix is assumed). (or <A href="help/topics/Misc/AddressExpressions.htm">Address Expression</A>) to specify
&nbsp;For those processors with multiple address-spaces, a pull-down is also provided 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 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> </I>OTHER</I> non-loaded space are only included if the <B>Include OTHER overlay spaces</B>
checkbox is selected.<BR> checkbox is selected.<BR>

View file

@ -124,7 +124,10 @@
range is smaller, then value associations for address range that was trucated is 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 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 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> </P>
</DIV> </DIV>
</DIV> </DIV>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,7 +28,7 @@ import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; 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.HelpLocation;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -82,9 +82,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
setOkEnabled(false); setOkEnabled(false);
changing = true; changing = true;
if (!isVisible()) { if (!isVisible()) {
AddressFactory factory = model.getAddressFactory(); Program program = model.getProgram();
newStartField.setAddressFactory(factory); newStartField.setProgram(program);
newEndField.setAddressFactory(factory); newEndField.setProgram(program);
} }
Address newStart = model.getNewStartAddress(); Address newStart = model.getNewStartAddress();
if (newStart != null) { if (newStart != null) {
@ -136,8 +136,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
} }
private JPanel buildMainPanel() { private JPanel buildMainPanel() {
JPanel panel = new JPanel(new PairLayout(5, 20, 150)); JPanel panel = new JPanel(new PairLayout(2, 10, 150));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); panel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
blockNameLabel = new GDLabel(".text"); blockNameLabel = new GDLabel(".text");
blockNameLabel.setName("blockName"); // name components for junits blockNameLabel.setName("blockName"); // name components for junits
@ -150,15 +150,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
lengthLabel = new GDLabel("4096 (0x1000)"); lengthLabel = new GDLabel("4096 (0x1000)");
lengthLabel.setName("length"); lengthLabel.setName("length");
newStartField = new AddressInput(); newStartField = new AddressInput(model.getProgram(), this::startChanged);
newStartField.setName("newStart"); newStartField.setName("newStart");
newEndField = new AddressInput(); newEndField = new AddressInput(model.getProgram(), this::endChanged);
newEndField.setName("newEnd"); newEndField.setName("newEnd");
newStartField.addChangeListener(e -> startChanged());
newEndField.addChangeListener(e -> endChanged());
panel.add(new GLabel("Name:", SwingConstants.RIGHT)); panel.add(new GLabel("Name:", SwingConstants.RIGHT));
panel.add(blockNameLabel); panel.add(blockNameLabel);
panel.add(new GLabel("Start Address:", SwingConstants.RIGHT)); panel.add(new GLabel("Start Address:", SwingConstants.RIGHT));
@ -174,13 +171,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
return panel; return panel;
} }
private void startChanged() { private void startChanged(Address address) {
if (changing) { if (changing) {
return; return;
} }
Address newStart = newStartField.getAddress(); if (address != null) {
if (newStart != null) { model.setNewStartAddress(address);
model.setNewStartAddress(newStart);
} }
else { else {
setStatusText("Invalid Address"); setStatusText("Invalid Address");
@ -188,13 +184,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
} }
} }
private void endChanged() { private void endChanged(Address address) {
if (changing) { if (changing) {
return; return;
} }
Address newEnd = newEndField.getAddress(); if (address != null) {
if (newEnd != null) { model.setNewEndAddress(address);
model.setNewEndAddress(newEnd);
} }
else { else {
setStatusText("Invalid Address"); 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.MoveBlockListener;
import ghidra.app.cmd.memory.MoveBlockTask; import ghidra.app.cmd.memory.MoveBlockTask;
import ghidra.framework.model.*; 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.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
@ -183,8 +184,8 @@ class MoveBlockModel implements DomainObjectListener {
program = null; program = null;
} }
AddressFactory getAddressFactory() { Program getProgram() {
return program.getAddressFactory(); return program;
} }
private Address getEndAddress(Address start) { 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.plugin.core.misc.RegisterField;
import ghidra.app.util.AddressInput; import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics; 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.Memory;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@ -50,16 +52,14 @@ class SplitBlockDialog extends DialogComponentProvider {
private JTextField blockTwoEndField; private JTextField blockTwoEndField;
private RegisterField blockTwoLengthField; private RegisterField blockTwoLengthField;
private MemoryBlock block; private MemoryBlock block;
private AddressFactory addrFactory;
private MemoryMapPlugin plugin; private MemoryMapPlugin plugin;
SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, AddressFactory af) { SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, Program program) {
super("Split Block"); super("Split Block");
this.plugin = plugin; this.plugin = plugin;
this.block = block; this.block = block;
addrFactory = af;
setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP, "Split Block")); setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP, "Split Block"));
addWorkPanel(create()); addWorkPanel(create(program));
addOKButton(); addOKButton();
addCancelButton(); addCancelButton();
setOkEnabled(false); setOkEnabled(false);
@ -89,8 +89,8 @@ class SplitBlockDialog extends DialogComponentProvider {
* Create the work panel. * Create the work panel.
* @return JPanel * @return JPanel
*/ */
private JPanel create() { private JPanel create(Program program) {
JPanel panelOne = new JPanel(new PairLayout(5, 5, 150)); JPanel panelOne = new JPanel(new PairLayout(5, 10, 150));
panelOne.setBorder(BorderFactory.createTitledBorder("Block to Split")); panelOne.setBorder(BorderFactory.createTitledBorder("Block to Split"));
blockOneNameField = new JTextField(10); blockOneNameField = new JTextField(10);
blockOneNameField.setName("BlockOneName"); blockOneNameField.setName("BlockOneName");
@ -99,7 +99,7 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneStartField.setName("BlockOneStart"); blockOneStartField.setName("BlockOneStart");
blockOneStartField.getAccessibleContext().setAccessibleName("Address of Block To Split"); blockOneStartField.getAccessibleContext().setAccessibleName("Address of Block To Split");
blockOneEnd = new AddressInput(); blockOneEnd = new AddressInput(program, this::blockOneEndChanged);
blockOneEnd.setName("BlockOneEnd"); blockOneEnd.setName("BlockOneEnd");
blockOneEnd.setAccessibleName("New Block End Adddress"); blockOneEnd.setAccessibleName("New Block End Adddress");
@ -121,7 +121,7 @@ class SplitBlockDialog extends DialogComponentProvider {
blockTwoNameField = new JTextField(10); blockTwoNameField = new JTextField(10);
blockTwoNameField.setName("BlockTwoName"); blockTwoNameField.setName("BlockTwoName");
blockTwoNameField.getAccessibleContext().setAccessibleName("Name of New Block"); blockTwoNameField.getAccessibleContext().setAccessibleName("Name of New Block");
blockTwoStart = new AddressInput(); blockTwoStart = new AddressInput(program, this::blockTwoStartChanged);
blockTwoStart.setName("BlockTwoStart"); blockTwoStart.setName("BlockTwoStart");
blockTwoStart.setAccessibleName("New Block Start Address"); blockTwoStart.setAccessibleName("New Block Start Address");
blockTwoEndField = new JTextField(10); blockTwoEndField = new JTextField(10);
@ -141,11 +141,11 @@ class SplitBlockDialog extends DialogComponentProvider {
panelTwo.add(blockTwoLengthField); panelTwo.add(blockTwoLengthField);
JPanel mainPanel = new JPanel(); JPanel mainPanel = new JPanel();
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
BoxLayout bl = new BoxLayout(mainPanel, BoxLayout.Y_AXIS); BoxLayout bl = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(bl); mainPanel.setLayout(bl);
mainPanel.add(Box.createVerticalStrut(5));
mainPanel.add(panelOne); mainPanel.add(panelOne);
mainPanel.add(Box.createVerticalStrut(10)); mainPanel.add(Box.createVerticalStrut(20));
mainPanel.add(panelTwo); mainPanel.add(panelTwo);
return mainPanel; return mainPanel;
@ -166,7 +166,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneStartField.setText(startAddr.toString()); blockOneStartField.setText(startAddr.toString());
blockOneStartField.setEnabled(false); blockOneStartField.setEnabled(false);
blockOneEnd.setAddressFactory(addrFactory);
blockOneEnd.setAddress(endAddr); blockOneEnd.setAddress(endAddr);
blockOneEnd.setAddressSpaceEditable(false); blockOneEnd.setAddressSpaceEditable(false);
@ -174,7 +173,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockTwoNameField.setText(name + ".split"); blockTwoNameField.setText(name + ".split");
blockTwoStart.setAddressFactory(addrFactory);
blockTwoStart.setAddress(startAddr); blockTwoStart.setAddress(startAddr);
blockTwoStart.setAddressSpaceEditable(false); blockTwoStart.setAddressSpaceEditable(false);
@ -189,8 +187,6 @@ class SplitBlockDialog extends DialogComponentProvider {
blockOneLengthField.setChangeListener(new LengthChangeListener(blockOneLengthField)); blockOneLengthField.setChangeListener(new LengthChangeListener(blockOneLengthField));
blockTwoLengthField.setChangeListener(new LengthChangeListener(blockTwoLengthField)); blockTwoLengthField.setChangeListener(new LengthChangeListener(blockTwoLengthField));
blockOneEnd.addChangeListener(new AddressChangeListener(blockOneEnd));
blockTwoStart.addChangeListener(new AddressChangeListener(blockTwoStart));
ActionListener al = e -> setStatusText(""); ActionListener al = e -> setStatusText("");
blockOneLengthField.addActionListener(al); blockOneLengthField.addActionListener(al);
@ -307,55 +303,27 @@ class SplitBlockDialog extends DialogComponentProvider {
} }
} }
/** private void blockOneEndChanged(Address end) {
* Listener on the AddressInput fields; update other fields when either
* of these fields change.
*/
private class AddressChangeListener implements ChangeListener {
AddressInput source;
public AddressChangeListener(AddressInput source) {
this.source = source;
}
@Override
public void stateChanged(ChangeEvent event) {
setStatusText(""); setStatusText("");
boolean ok = false; boolean isValid = checkBlockOneEndAddress(end);
if (source == blockOneEnd) { setOkEnabled(isValid);
ok = blockOneEndChanged();
}
else if (source == blockTwoStart) {
ok = blockTwoStartChanged();
}
setOkEnabled(ok);
} }
private Address getAddress() throws InvalidInputException { private void blockTwoStartChanged(Address start) {
setStatusText("");
AddressInput field = source; boolean isValid = checkBlockTwoStartChanged(start);
Address addr = field.getAddress(); setOkEnabled(isValid);
if (addr == null && field.hasInput()) {
throw new InvalidInputException();
}
return addr;
} }
private boolean blockOneEndChanged() { private boolean checkBlockOneEndAddress(Address end) {
Address start = block.getStart();
Address end = null; if (end == null) {
try {
end = getAddress();
}
catch (InvalidInputException e) {
setStatusText("Invalid Address"); setStatusText("Invalid Address");
return false; return false;
} }
if (end == null) { Address start = block.getStart();
return false;
}
if (end.compareTo(start) < 0) { if (end.compareTo(start) < 0) {
setStatusText("End address must be greater than start"); setStatusText("End address must be greater than start");
return false; return false;
@ -393,14 +361,11 @@ class SplitBlockDialog extends DialogComponentProvider {
return false; return false;
} }
return true; return true;
} }
private boolean blockTwoStartChanged() { private boolean checkBlockTwoStartChanged(Address start) {
Address start = null; if (start == null) {
try {
start = getAddress();
}
catch (InvalidInputException e) {
setStatusText("Invalid Address"); setStatusText("Invalid Address");
return false; return false;
} }
@ -435,7 +400,4 @@ class SplitBlockDialog extends DialogComponentProvider {
} }
return true; return true;
} }
}
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,34 +16,44 @@
package ghidra.app.util; package ghidra.app.util;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.FontMetrics; import java.awt.CardLayout;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; 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.combobox.GComboBox;
import docking.widgets.table.FocusableEditor; import docking.widgets.table.FocusableEditor;
import docking.widgets.textfield.HexDecimalModeTextField;
import generic.expressions.ExpressionException;
import ghidra.program.model.address.*; 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 * Input field for entering address or address expression. Handles multiple address
* spaces. * spaces and supports both hex and decimal number modes for evaluating numbers.
*/ */
public class AddressInput extends JPanel implements FocusableEditor { public class AddressInput extends JPanel implements FocusableEditor {
private JTextField textField; public final static Predicate<AddressSpace> ALL_MEMORY_SPACES = s -> s.isMemorySpace();
private JComboBox<AddressSpace> combo; 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 boolean comboAdded;
private AddressFactory addrFactory; private boolean assumeHex = true;
private ChangeListener changeListener; private boolean notificationsEnabled = true;
private boolean updatingAddress;
private boolean updateSpaceField;
private boolean stateChanging;
private JTextField spaceField;
private static final Comparator<AddressSpace> ADDRESS_SPACE_SORT_COMPARATOR = (s1, s2) -> { private static final Comparator<AddressSpace> ADDRESS_SPACE_SORT_COMPARATOR = (s1, s2) -> {
if (s1.isOverlaySpace()) { if (s1.isOverlaySpace()) {
@ -58,65 +68,132 @@ public class AddressInput extends JPanel implements FocusableEditor {
}; };
/** /**
* Constructor for AddressInput. * Constructs an AddressInput field with no specified program or address.
* @param border border around each subcomponent (combo/text fields).
*/ */
public AddressInput(Border border) { public AddressInput() {
this(); this(null, null, null);
combo.setBorder(border); }
/**
* 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); 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() { public void setAssumeHex(boolean hexMode) {
textField.setHexMode(hexMode);
setLayout(new BorderLayout()); hexModeChanged(hexMode);
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());
} }
/** /**
* Set the field to display the given address * Set the field to display the given address
* @param address the new address to display
*/ */
public void setAddress(Address addr) { public void setAddress(Address address) {
if (stateChanging) { if (address.equals(getAddress())) {
// called while we are in doing a state changed notification
return; return;
} }
updatingAddress = true; notificationsEnabled = false;
textField.setText(addr.toString(false)); try {
combo.setSelectedItem(addr.getAddressSpace()); String addressString = address.toString(false);
updatingAddress = false; addressString = removeLeadingZeros(addressString);
if (updateSpaceField) { if (!assumeHex) {
updateSpaceField = false; addressString = "0x" + addressString;
spaceField.setText(addr.getAddressSpace().getName()); }
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 * Returns the address in the field or null if the address can't
* be parsed. * be parsed.
* @return The address for the current value in the text field * @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. * @throws NullPointerException if AddressFactory has not been set.
*/ */
public Address getAddress() { public Address getAddressWithExceptions() throws ExpressionException {
String addrStr = textField.getText(); String addrExpression = textField.getText();
if (addrExpression.isBlank()) {
AddressSpace space = getAddressSpace(); return null;
try {
return space.getAddress(addrStr);
} }
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; return null;
} }
} }
/** /**
* Returns the address space selected in the combobox or in the input text itself * Returns the address space selected in the combobox the default address space if the
* if specified (eg: "register:1"). If the address space is not specified; returns the * comboBox is not being shown.
* default space.
* *
* @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() { public AddressSpace getAddressSpace() {
if (comboAdded) { return addressSpaceField.getAddressSpace();
return (AddressSpace) combo.getSelectedItem();
}
return addrFactory.getDefaultAddressSpace();
} }
/** /**
* Returns true if the Address input field contains text. * Returns true if the Address input field contains text.
* The getAddress() method will return null if text is not * The getAddress() method will return null if text is not
* a valid address. * a valid address.
* @return true if the address field is not blank
*/ */
public boolean hasInput() { 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 * @return the text in this field
*/ */
public String getText() { public String getText() {
return textField.getText(); return textField.getText().trim();
}
public AddressFactory getAddressFactory() {
return addrFactory;
} }
/** /**
* Address Space predicate which includes all loaded memory spaces. * Set the program to be used to parse addresses and expressions and also
* See {@link AddressSpace#isLoadedMemorySpace()}. * to determine the list of valid address spaces. Only loaded memory spaces
* 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
* will be allowed (see {@link AddressSpace#isLoadedMemorySpace()}). * 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) { 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 * Sets a consumer to be notified when the address input field changes, but can't be parsed
* with the list of valid address spaces if there is more than one space. The specified * into a valid address.
* predicate will be used to determine if an address space should be included. * @param addressErrorConsumer the consumer to be notified for bad address input
* @param factory address factory to use
* @param predicate callback used to determine if an address space should be included for selection
*/ */
public void setAddressFactory(AddressFactory factory, Predicate<AddressSpace> predicate) { public void setAddressErrorConsumer(Consumer<String> addressErrorConsumer) {
this.addrFactory = factory; this.addressErrorConsumer = addressErrorConsumer;
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);
}
} }
/** /**
@ -273,32 +313,15 @@ public class AddressInput extends JPanel implements FocusableEditor {
textField.selectAll(); 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. * Set the address space and offset.
* NOTE: Unlike {@link #setAddress(Address)} this method is intended for test use only * 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 * @param addr the address value
*/ */
public void setValue(Address addr) { public void simulateAddressChanged(Address addr) {
setAddress(addr); setAddress(addr);
stateChanged(); notifyAddressChanged();
} }
@Override @Override
@ -318,27 +341,7 @@ public class AddressInput extends JPanel implements FocusableEditor {
* @param state false means that the combo box should not be editable * @param state false means that the combo box should not be editable
*/ */
public void setAddressSpaceEditable(boolean state) { public void setAddressSpaceEditable(boolean state) {
if (!state && comboAdded) { addressSpaceField.setEditable(state);
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;
} }
/** /**
@ -352,20 +355,17 @@ public class AddressInput extends JPanel implements FocusableEditor {
/** /**
* Removes the action listener from the list to be notified. * Removes the action listener from the list to be notified.
* @param listener * @param listener the listener to be removed
*/ */
public void removeActionListener(ActionListener listener) { public void removeActionListener(ActionListener listener) {
this.textField.removeActionListener(listener); this.textField.removeActionListener(listener);
} }
/**
* @see java.awt.Component#setEnabled(boolean)
*/
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
super.setEnabled(enabled); super.setEnabled(enabled);
textField.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) { public void setEditable(boolean b) {
textField.setEditable(state); 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() { public boolean isEditable() {
return textField.isEditable(); return textField.isEditable();
} }
@Override @Override
public void focusEditor() { public void focusEditor() {
if (comboAdded) { if (addressSpaceField.getSpaceCount() > 1 && addressSpaceField.isEnabled()) {
combo.requestFocusInWindow(); addressSpaceField.requestFocusInWindow();
} }
else { else {
textField.requestFocusInWindow(); 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 @Override
public void requestFocus() { public void requestFocus() {
textField.requestFocus(); textField.requestFocus();
} }
protected JTextField getAddressTextField() { private void buildComponent() {
return textField; 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();
} }
protected JTextField getAddressSpaceTextField() { @Override
return spaceField; public void removeUpdate(DocumentEvent e) {
notifyAddressChanged();
} }
@Override
public void changedUpdate(DocumentEvent e) {
notifyAddressChanged();
}
});
}
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:"); JLabel minLabel = new GDLabel("Min:");
minLabel.setToolTipText("Enter minimum address to add or remove"); minLabel.setToolTipText("Enter minimum address to add or remove");
minAddressPanel.add(minLabel, BorderLayout.WEST); minAddressPanel.add(minLabel, BorderLayout.WEST);
minAddressField = new AddressInput(); minAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton());
minAddressField.setAddressFactory(addressFactory);
ChangeListener listener = e -> validateAddRemoveButton();
minAddressField.addChangeListener(listener);
minAddressPanel.add(minAddressField, BorderLayout.CENTER); minAddressPanel.add(minAddressField, BorderLayout.CENTER);
JPanel maxAddressPanel = new JPanel(); JPanel maxAddressPanel = new JPanel();
@ -78,9 +75,7 @@ public class AddressSetEditorPanel extends JPanel {
JLabel maxLabel = new GDLabel("Max:"); JLabel maxLabel = new GDLabel("Max:");
maxLabel.setToolTipText("Enter maximum address to add or remove"); maxLabel.setToolTipText("Enter maximum address to add or remove");
maxAddressPanel.add(maxLabel, BorderLayout.WEST); maxAddressPanel.add(maxLabel, BorderLayout.WEST);
maxAddressField = new AddressInput(); maxAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton());
maxAddressField.setAddressFactory(addressFactory);
maxAddressField.addChangeListener(listener);
maxAddressPanel.add(maxAddressField, BorderLayout.CENTER); maxAddressPanel.add(maxAddressField, BorderLayout.CENTER);
maxAddressPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); maxAddressPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
addRangeButton = new GButton(ADD_ICON); addRangeButton = new GButton(ADD_ICON);

View file

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

View file

@ -18,9 +18,9 @@ package ghidra.app.util.cparser.CPP;
import java.util.*; import java.util.*;
import generic.expressions.ExpressionEvaluator;
import ghidra.app.util.cparser.CPP.PreProcessor.PPToken; import ghidra.app.util.cparser.CPP.PreProcessor.PPToken;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.util.AddressEvaluator;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
@ -288,7 +288,6 @@ public class DefineTable {
sublist.addAll(initialList); sublist.addAll(initialList);
} }
// don't replace an infinite number of times. Fail safe for possible ininite loop // don't replace an infinite number of times. Fail safe for possible ininite loop
while (pos < buf.length() && replaceCount < ARBITRARY_MAX_REPLACEMENTS) { while (pos < buf.length() && replaceCount < ARBITRARY_MAX_REPLACEMENTS) {
// clear list of used macros when move past replacement area // clear list of used macros when move past replacement area
@ -353,7 +352,8 @@ public class DefineTable {
return true; 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; String replacementString = null;
if (sublist == null) { if (sublist == null) {
@ -455,7 +455,7 @@ public class DefineTable {
// Handle "..." varargs // Handle "..." varargs
// if last argument is ellipsis, then is varargs, replace the rest of the params // if last argument is ellipsis, then is varargs, replace the rest of the params
String curArgName = argv.elementAt(index).image; 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; isVarArg = true;
// Replace __VA_ARGS__ with the rest of params // Replace __VA_ARGS__ with the rest of params
curArgName = "__VA_ARGS__"; curArgName = "__VA_ARGS__";
@ -550,22 +550,23 @@ public class DefineTable {
return; return;
} }
int lparen = buf.indexOf("(", optIdx+1); int lparen = buf.indexOf("(", optIdx + 1);
if (lparen < 0) { if (lparen < 0) {
return; return;
} }
int rparen = buf.indexOf(")",lparen+1); int rparen = buf.indexOf(")", lparen + 1);
if (rparen < 0) { if (rparen < 0) {
return; return;
} }
// get in between string. // get in between string.
String replarg = buf.substring(lparen+1, rparen); String replarg = buf.substring(lparen + 1, rparen);
if (hadVarArgs) { if (hadVarArgs) {
buf.replace(optIdx, rparen+1, replarg); buf.replace(optIdx, rparen + 1, replarg);
} else { }
buf.replace(optIdx, rparen+1, ""); else {
buf.replace(optIdx, rparen + 1, "");
} }
} }
@ -720,7 +721,7 @@ public class DefineTable {
if (lvalue == null) { if (lvalue == null) {
try { try {
lvalue = AddressEvaluator.evaluateToLong(strValue); lvalue = ExpressionEvaluator.evaluateToLong(strValue);
} }
catch (Exception exc) { catch (Exception exc) {
// ignore didn't parse well // ignore didn't parse well
@ -738,7 +739,8 @@ public class DefineTable {
dtMgr.endTransaction(transactionID, true); 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; String enumName = prefix + defName;
// Start the Enum at 8, then resize to fit the value // Start the Enum at 8, then resize to fit the value

View file

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

View file

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

View file

@ -231,7 +231,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength"); (RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockOneEnd.setValue("01003000")); runSwing(() -> blockOneEnd.setText("01003000"));
assertEquals(0x2001, blockOneLength.getValue().longValue()); assertEquals(0x2001, blockOneLength.getValue().longValue());
assertEquals(getAddr(0x01003001), blockTwoStart.getAddress()); assertEquals(getAddr(0x01003001), blockTwoStart.getAddress());
assertEquals("010075ff", blockTwoEnd.getText()); assertEquals("010075ff", blockTwoEnd.getText());
@ -277,7 +277,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength"); (RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockTwoStart.setValue("01003000")); runSwing(() -> blockTwoStart.setText("01003000"));
assertEquals(0x2000, blockOneLength.getValue().longValue()); assertEquals(0x2000, blockOneLength.getValue().longValue());
assertEquals(getAddr(0x01002fff), blockOneEnd.getAddress()); assertEquals(getAddr(0x01002fff), blockOneEnd.getAddress());
assertEquals("010075ff", blockTwoEnd.getText()); assertEquals("010075ff", blockTwoEnd.getText());
@ -357,7 +357,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(AddressInput) findComponentByName(d.getComponent(), "BlockOneEnd"); (AddressInput) findComponentByName(d.getComponent(), "BlockOneEnd");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockOneEnd.setValue("01000")); runSwing(() -> blockOneEnd.setText("01000"));
assertFalse(okButton.isEnabled()); assertFalse(okButton.isEnabled());
assertEquals("End address must be greater than start", assertEquals("End address must be greater than start",
findLabelStr(d.getComponent(), "statusLabel")); findLabelStr(d.getComponent(), "statusLabel"));
@ -378,7 +378,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
(AddressInput) findComponentByName(d.getComponent(), "BlockTwoStart"); (AddressInput) findComponentByName(d.getComponent(), "BlockTwoStart");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> blockTwoStart.setValue("01000")); runSwing(() -> blockTwoStart.setText("01000"));
assertFalse(okButton.isEnabled()); assertFalse(okButton.isEnabled());
assertEquals("Start address must be greater than original block start (01001000)", assertEquals("Start address must be greater than original block start (01001000)",
findLabelStr(d.getComponent(), "statusLabel")); findLabelStr(d.getComponent(), "statusLabel"));
@ -531,7 +531,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength"); RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("00002000")); runSwing(() -> start.setText("00002000"));
assertEquals("0x1005600", length.getText()); assertEquals("0x1005600", length.getText());
assertTrue(okButton.isEnabled()); assertTrue(okButton.isEnabled());
@ -579,7 +579,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
assertNotNull(length); assertNotNull(length);
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("01201000")); runSwing(() -> start.setText("01201000"));
assertFalse(okButton.isEnabled()); assertFalse(okButton.isEnabled());
assertEquals("Start must be less than 01001000", assertEquals("Start must be less than 01001000",
findLabelStr(d.getComponent(), "statusLabel")); findLabelStr(d.getComponent(), "statusLabel"));
@ -681,7 +681,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
JTextField end = (JTextField) findComponentByName(d.getComponent(), "EndAddress"); JTextField end = (JTextField) findComponentByName(d.getComponent(), "EndAddress");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> start.setValue("01008000")); runSwing(() -> start.setText("01008000"));
assertEquals("0100f3ff", end.getText()); assertEquals("0100f3ff", end.getText());
assertTrue(okButton.isEnabled()); assertTrue(okButton.isEnabled());
@ -740,7 +740,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength"); RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> end.setValue("01007700")); runSwing(() -> end.setText("01007700"));
assertEquals("0x6701", length.getText()); assertEquals("0x6701", length.getText());
assertTrue(okButton.isEnabled()); assertTrue(okButton.isEnabled());
@ -798,7 +798,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest
AddressInput end = (AddressInput) findComponentByName(d.getComponent(), "EndAddress"); AddressInput end = (AddressInput) findComponentByName(d.getComponent(), "EndAddress");
JButton okButton = findButton(d.getComponent(), "OK"); JButton okButton = findButton(d.getComponent(), "OK");
runSwing(() -> end.setValue("01007000")); runSwing(() -> end.setText("01007000"));
assertFalse(okButton.isEnabled()); assertFalse(okButton.isEnabled());
assertEquals("End must be greater than 010075ff", assertEquals("End must be greater than 010075ff",
findLabelStr(d.getComponent(), "statusLabel")); 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 docking.widgets.values.AbstractValue;
import ghidra.app.util.AddressInput; import ghidra.app.util.AddressInput;
import ghidra.features.base.values.AddressValue;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
@ -144,7 +143,7 @@ public class AddressValueTest extends AbstractValueIntegrationTest {
protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) { protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> { runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent(); 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) { protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> { runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent(); 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 static org.junit.Assert.*;
import org.junit.Test; import org.junit.*;
import generic.test.AbstractGenericTest;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
/** public class AddressEvaluatorTest extends AbstractGenericTest {
*
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class AddressEvaluatorTest extends AbstractGhidraHeadedIntegrationTest {
AddressFactory addrFactory; private Program program;
private ProgramBuilder builder;
private Symbol entry;
private Symbol entryInFoo;
public AddressEvaluatorTest() { public AddressEvaluatorTest() {
super(); super();
} }
private Address addr(String address) { @Before
return addrFactory.getAddress(address); 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 @Test
public void testEval() throws Exception { public void testLongValueExpression() {
Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this); assertEval(addr("0x19"), "(2+3)*5");
addrFactory = p.getAddressFactory(); assertEval(addr("0x11"), "2+3*5");
int txId = p.startTransaction("Test"); assertEval(addr("0x11"), "2+(3*5)");
try { assertEval(addr("0x3"), "0-5+8");
assertEquals(addr("0x19"), AddressEvaluator.evaluate(p, "(2+3)*5")); assertEval(addr("0x3"), "-5+8");
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+3*5")); assertEval(addr("0xfffffffB"), "-5");
assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+(3*5)")); assertEval(addr("0x11"), "3+(5+(3*2)+(3))");
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"));
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)"));
assertEquals(addr("0x1"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) >= 0x1)"));
assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) <= 0x1)"));
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)"));
assertEquals(null, AddressEvaluator.evaluate(p, "( 4P)"));
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); @Test
p.release(this); public void testAssumesHex() {
assertEval(addr("0x30"), "20 + 10");
assertEval(addr("0xf1"), "e1+10");
} }
@Test
public void testAcceptsHexPrefix() {
assertEval(addr("0x16"), "0x11+5");
assertEval(addr("0x35"), "20+0x15");
}
@Test
public void testBitWiseExpressions() {
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)");
}
@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 @Test
public void testMultiAddrSpace() throws Exception { public void testMultiAddrSpace() throws Exception {
Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this); assertEval(addr("0x15"), "ram:2 + 0x13");
addrFactory = p.getAddressFactory(); assertEval(addr("register:0x15"), "register:2 + 0x13");
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); 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) { if (provider instanceof ProgramByteViewerComponentProvider) {
Program program = ((ProgramByteViewerComponentProvider) provider).getProgram(); Program program = ((ProgramByteViewerComponentProvider) provider).getProgram();
if (program != null) { if (program != null) {
addressInputField = new AddressInput(); Address alignment = getAlignmentAddress();
addressInputField.setAddressFactory(program.getAddressFactory()); addressInputField = new AddressInput(program, a -> update());
addressInputField.showAddressSpaceCombo(false); addressInputField.setAddressSpaceFilter(s -> s == alignment.getAddressSpace());
addressInputField.setAddress(getAlignmentAddress()); addressInputField.setAddress(alignment);
panel.add(addressInputField); panel.add(addressInputField);
addressInputField.addChangeListener(this);
addressInputField.setAccessibleName("Alignment Address"); addressInputField.setAccessibleName("Alignment Address");
} }
} }
@ -214,13 +213,13 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider
} }
private boolean hasValidFieldValues() { private boolean hasValidFieldValues() {
if (addressInputField.getValue().length() == 0) { if (addressInputField.getText().length() == 0) {
setStatusText("Enter an alignment address"); setStatusText("Enter an alignment address");
return false; return false;
} }
Address alignmentAddress = addressInputField.getAddress(); Address alignmentAddress = addressInputField.getAddress();
if (alignmentAddress == null) { if (alignmentAddress == null) {
setStatusText("Invalid alignment address:" + addressInputField.getValue()); setStatusText("Invalid alignment address:" + addressInputField.getText());
return false; return false;
} }
BigInteger bytesPerLine = bytesPerLineField.getValue(); BigInteger bytesPerLine = bytesPerLineField.getValue();

View file

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

View file

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

View file

@ -24,7 +24,6 @@ import docking.widgets.label.GDLabel;
import ghidra.app.util.AddressInput; import ghidra.app.util.AddressInput;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -32,7 +31,6 @@ import ghidra.util.layout.PairLayout;
public class AddRemoveAddressRangeDialog extends DialogComponentProvider { public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
private Program program; private Program program;
private AddressFactory addressFactory;
private AddressRangeListener listener; private AddressRangeListener listener;
private JPanel addressRangePanel; private JPanel addressRangePanel;
@ -45,7 +43,6 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
AddressRangeListener listener) { AddressRangeListener listener) {
super(programIndicator + " Address Range", true, true, true, false); super(programIndicator + " Address Range", true, true, true, false);
this.program = program; this.program = program;
addressFactory = program.getAddressFactory();
this.listener = listener; this.listener = listener;
setHelpLocation(new HelpLocation(HelpTopics.LABEL, "AddEditDialog")); setHelpLocation(new HelpLocation(HelpTopics.LABEL, "AddEditDialog"));
addWorkPanel(createAddressRangePanel()); addWorkPanel(createAddressRangePanel());
@ -68,8 +65,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
minLabel.setToolTipText("Enter minimum address to add or remove"); minLabel.setToolTipText("Enter minimum address to add or remove");
addressRangePanel.add(minLabel); addressRangePanel.add(minLabel);
minAddressField = new AddressInput(); minAddressField = new AddressInput(program);
minAddressField.setAddressFactory(addressFactory);
Dimension minPreferredSize = getPreferredSize(); Dimension minPreferredSize = getPreferredSize();
minPreferredSize.width = 200; minPreferredSize.width = 200;
minAddressField.setPreferredSize(minPreferredSize); minAddressField.setPreferredSize(minPreferredSize);
@ -79,8 +75,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider {
maxLabel.setToolTipText("Enter maximum address to add or remove"); maxLabel.setToolTipText("Enter maximum address to add or remove");
addressRangePanel.add(maxLabel); addressRangePanel.add(maxLabel);
maxAddressField = new AddressInput(); maxAddressField = new AddressInput(program);
maxAddressField.setAddressFactory(addressFactory);
Dimension maxPreferredSize = getPreferredSize(); Dimension maxPreferredSize = getPreferredSize();
maxPreferredSize.width = 200; maxPreferredSize.width = 200;
minAddressField.setPreferredSize(maxPreferredSize); 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; package docking.widgets.textfield;
import java.awt.*; import java.awt.event.ActionListener;
import java.awt.event.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.*; import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.event.*; import javax.swing.event.*;
import javax.swing.text.*; 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; import ghidra.util.SystemUtilities;
/** /**
@ -63,12 +59,11 @@ import ghidra.util.SystemUtilities;
*/ */
public class IntegerTextField { public class IntegerTextField {
private JTextField textField; private HexDecimalModeTextField textField;
private boolean isHexMode = false; private boolean isHexMode = false;
private boolean allowsNegative = true; private boolean allowsNegative = true;
private boolean allowsHexPrefix = true; private boolean allowsHexPrefix = true;
private boolean showNumbericDecoration = true;
private BigInteger maxValue; private BigInteger maxValue;
private BigInteger minValue; private BigInteger minValue;
@ -109,9 +104,13 @@ public class IntegerTextField {
* @param initialValue the initial value * @param initialValue the initial value
*/ */
public IntegerTextField(int columns, BigInteger initialValue) { 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); setValue(initialValue);
textField.getDocument().addDocumentListener(new DocumentListener() {
document.addDocumentListener(new DocumentListener() {
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
@ -266,8 +265,7 @@ public class IntegerTextField {
* @param show true to show the radix mode. * @param show true to show the radix mode.
*/ */
public void setShowNumberMode(boolean show) { public void setShowNumberMode(boolean show) {
this.showNumbericDecoration = show; textField.setShowNumberMode(show);
textField.repaint();
} }
/** /**
@ -278,9 +276,15 @@ public class IntegerTextField {
* the current value from decimal to hex. * the current value from decimal to hex.
*/ */
public void setHexMode() { public void setHexMode() {
BigInteger currentValue = getValue(); BigInteger value = getValue();
isHexMode = true; setHexMode(true);
updateTextField(currentValue); 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. * current value from hex to decimal.
*/ */
public void setDecimalMode() { public void setDecimalMode() {
BigInteger currentValue = getValue(); BigInteger value = getValue();
isHexMode = false; setHexMode(false);
updateTextField(currentValue); setValue(value);
} }
/** /**
@ -477,6 +481,12 @@ public class IntegerTextField {
textField.setHorizontalAlignment(alignment); textField.setHorizontalAlignment(alignment);
} }
private void textFieldHexModeChanged(boolean hexMode) {
BigInteger value = getValue();
this.isHexMode = hexMode;
setValue(value);
}
private String computeTextForValue(BigInteger value) { private String computeTextForValue(BigInteger value) {
if (value == null) { if (value == null) {
return ""; return "";
@ -525,15 +535,6 @@ public class IntegerTextField {
} }
} }
private void toggleMode() {
if (isHexMode) {
setDecimalMode();
}
else {
setHexMode();
}
}
private boolean passesMaxCheck(BigInteger value) { private boolean passesMaxCheck(BigInteger value) {
if (value == null) { if (value == null) {
return true; return true;
@ -648,7 +649,7 @@ public class IntegerTextField {
if (isValidPrefix(valueString)) { if (isValidPrefix(valueString)) {
// When the input is valid, update the hex mode to match how the text was parsed. // When the input is valid, update the hex mode to match how the text was parsed.
// See parseAsHex variable comment above. // See parseAsHex variable comment above.
isHexMode = parseAsHex; setHexMode(parseAsHex);
return true; return true;
} }
@ -661,7 +662,7 @@ public class IntegerTextField {
if (passesMaxCheck(value) && passesMinCheck(value)) { if (passesMaxCheck(value) && passesMinCheck(value)) {
// When the input is valid, update the hex mode to match how the text was parsed. // When the input is valid, update the hex mode to match how the text was parsed.
// See parseAsHex variable comment above. // See parseAsHex variable comment above.
isHexMode = parseAsHex; setHexMode(parseAsHex);
return true; return true;
} }
} }
@ -692,76 +693,4 @@ public class IntegerTextField {
// so we don't allow negatives // so we don't allow negatives
return value.signum() < 0; 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; 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.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.mem.Memory;
import ghidra.program.model.symbol.SymbolTable; import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.NumericUtilities; import ghidra.program.model.symbol.*;
/** /**
* The <CODE>AddressEvaluator</CODE> class provides a way to evaluate a string * Class for evaluating expressions as an Address. See
* that represents an address and resolve it to an address for a particular program. * {@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 p the program to use for determining the address.
* @param baseAddr the base address to use for relative addressing. * @param inputExpression string representation of the address desired.
* @param s string representation of the address desired.
* @return the address. Otherwise, return null if the string fails to evaluate * @return the address. Otherwise, return null if the string fails to evaluate
* to a unique legitimate address. * to a unique legitimate address.
*/ */
public static Address evaluate(Program p, Address baseAddr, String s) { public static Address evaluate(Program p, String inputExpression) {
return evaluate(p, null, inputExpression);
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;
} }
/** /**
* 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 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 * @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) { public static Address evaluate(Program p, Address baseAddr, String inputExpression) {
return evaluate(p, null, s); 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 * Constructs an AddressEvalutor for the given program and in the specified hex/decimal mode.
* be a legitimate Address in the program's address space. This method is meant to provide a way of * @param program the program to use to evaluate expressions into valid addresses.
* creating an Address object from a sequence of bytes that can be used for additional tests and * @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers.
* 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.
*
*/ */
public static Address evaluate(Program p, byte[] addrBytes) { public AddressEvaluator(Program program, boolean assumeHex) {
this(program, null, assumeHex);
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. * Constructs an AdddressEvaluator without a full program. This version will not be able to
* Computing the number of bits to shift the current byte value * evaluate symbol or memory block names. This is mostly for backwards compatibility.
* is different for Little vs. Big Endian. Need to multiply by * @param factory the address factory for creating addresses
* 8 to shift in 1-byte increments. * @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers.
*/ */
if (isBigEndian) { public AddressEvaluator(AddressFactory factory, boolean assumeHex) {
index = 0; this(factory, null, assumeHex);
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(); /**
* 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);
}
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());
}
if (expressionValue instanceof LongExpressionValue longValue) {
long offset = longValue.getLongValue();
AddressSpace space = getAddressSpace();
try { try {
return space.getAddress(offset, true); return space.getAddressInThisSpaceOnly(offset);
} }
catch (AddressOutOfBoundsException e) { catch (AddressOutOfBoundsException e) {
return null; throw new ExpressionException(e.getMessage());
} }
} }
throw new ExpressionException("Expression did not evalute to a long! Got a " +
expressionValue.getClass() + " instead.");
}
private static Object getValueObject(SymbolTable st, AddressFactory af, String tok) { /**
* Returns the {@link AddressFactory} being used by this address evaluator
if (st == null || af == null) { * @return the {@link AddressFactory} being used by this address evaluator
return getValueObject(tok); */
public AddressFactory getAddressFactory() {
return addressFactory;
} }
try { /**
return NumericUtilities.parseHexLong(tok); * 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;
} }
catch (NumberFormatException e) {
// ignore // checks if the given address's address space is compatible with the preferred address space
} private Address validateAddressSpace(Address address) throws ExpressionException {
Address address = af.getAddress(tok); if (preferredSpace == null) {
if (address != null) {
return address; return address;
} }
AddressSpace space = address.getAddressSpace();
List<Symbol> globalSymbols = st.getLabelOrFunctionSymbols(tok, null); if (space.equals(preferredSpace)) {
if (globalSymbols.size() == 1) { return address;
return globalSymbols.get(0).getAddress();
} }
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) { private boolean isOverlayRelated(AddressSpace space1, AddressSpace space2) {
try { AddressSpace base1 = getBaseSpace(space1);
int start = 0; AddressSpace base2 = getBaseSpace(space2);
int radix = 10; return base1.equals(base2);
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) private AddressSpace getBaseSpace(AddressSpace space) {
: NumericUtilities.parseHexLong(strValue); if (space instanceof OverlayAddressSpace overlaySpace) {
return overlaySpace.getOverlayedSpace();
} }
catch (RuntimeException e) { return space;
// ignore
}
return null;
} }
private static Object eval(List<Object> list) { private AddressSpace getAddressSpace() {
if (preferredSpace != null) {
// first evaluate any grouped expressions return preferredSpace;
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)); return addressFactory.getDefaultAddressSpace();
if (obj == null) {
return null;
}
list.subList(i, i + 2).clear();
list.set(i, obj);
}
}
}
//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);
}
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);
}
}
}
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;
} }
@Override @Override
public String toString() { protected ExpressionValue evaluateSymbol(String input) {
return name; Address address = addressFactory.getAddress(input);
if (address != null) {
return new AddressExpressionValue(address);
} }
/** Program program = getProgram();
* Gets the static object implementation of an operator. if (program != null) {
* @param tok token string for the operator return getAddressForProgram(program, input);
* @return the static operator object.
*/
public static Operator getOperator(String tok) {
if (tok.equals("+")) {
return PLUS;
} }
if (tok.equals("&")) {
return AND; return null;
} }
if (tok.equals("|")) {
return OR; private ExpressionValue getAddressForProgram(Program program, String input) {
Address address = getAddressForSymbol(program, input);
if (address == null) {
address = getAddressFromMemoryMap(program, input);
} }
if (tok.equals("^")) {
return XOR; return address == null ? null : new AddressExpressionValue(address);
} }
else if (tok.equals("-")) {
return MINUS; private Address getAddressFromMemoryMap(Program program, String input) {
Memory memory = program.getMemory();
MemoryBlock block = memory.getBlock(input);
if (block != null) {
return block.getStart();
} }
else if (tok.equals("~")) { return null;
return NOT;
} }
else if (tok.equals("*")) {
return TIMES; 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;
} }
else if (tok.equals("/")) {
return DIVIDE;
} }
else if (tok.equals(")")) { SymbolTable symbolTable = program.getSymbolTable();
return RIGHT_PAREN; List<Symbol> symbols = symbolTable.getLabelOrFunctionSymbols(symbolName, namespace);
if (symbols.size() == 1) {
return symbols.get(0).getAddress();
} }
else if (tok.equals("(")) { return null;
return LEFT_PAREN;
} }
else if (tok.equals("<<")) {
return LEFTSHIFT; private Namespace getParentNamespace(Program program, SymbolPath path) {
if (path == null) {
return null;
} }
else if (tok.equals(">>")) { List<Namespace> spaces = NamespaceUtils.getNamespaceByPath(program, null, path.getPath());
return RIGHTSHIFT; if (spaces.size() == 1) {
return spaces.get(0);
} }
else if (tok.equals("==")) { return null;
return EQUALS;
} }
else if (tok.equals("!=")) {
return NOTEQUALS; private Program getProgram() {
} if (programReference != null) {
else if (tok.equals("<")) { return programReference.get();
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; 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 org.bouncycastle.util.Arrays;
import generic.expressions.ExpressionEvaluator;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility;
import ghidra.app.script.GhidraScript; 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.app.util.cparser.CPP.*;
import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager; import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.util.AddressEvaluator;
import ghidra.util.Msg; import ghidra.util.Msg;
public class CreateAVR8GDTArchiveScript extends GhidraScript { public class CreateAVR8GDTArchiveScript extends GhidraScript {
@ -230,7 +230,7 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript {
continue; continue;
} }
lvalue = AddressEvaluator.evaluateToLong(expandValue); lvalue = ExpressionEvaluator.evaluateToLong(expandValue);
if (lvalue == null) { if (lvalue == null) {
continue; continue;
} }

View file

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