diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 7f03be2e0c..72eaba576b 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -398,6 +398,7 @@ src/main/help/help/topics/MemoryMapPlugin/images/MemoryMap.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png||GHIDRA||||END| +src/main/help/help/topics/Misc/AddressExpressions.htm||GHIDRA||||END| src/main/help/help/topics/Misc/Appendix.htm||GHIDRA||||END| src/main/help/help/topics/Misc/Welcome_to_Ghidra_Help.htm||GHIDRA||||END| src/main/help/help/topics/Navigation/Navigation.htm||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml index 2b0315ae17..506c93713f 100644 --- a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml @@ -196,6 +196,8 @@ + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm b/Ghidra/Features/Base/src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm index b6e3b3149f..b7b035a1e8 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FallThroughPlugin/Override_Fallthrough.htm @@ -55,8 +55,9 @@
  • Select the User radio button.
  • -
  • Enter an address, or click in the Code Browser at the address of the new - fallthrough.
  • +
  • Enter an address + (or Address Expression), or click in + the Code Browser at the address of the new fallthrough.
  • Select the Apply button to change the fallthrough and leave the dialog intact; select the OK button to change the fallthrough and dismiss the dialog.
  • diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/Memory_Map.htm b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/Memory_Map.htm index cdf5e0adcb..542cbfd2e2 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/Memory_Map.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/Memory_Map.htm @@ -243,8 +243,10 @@

    Block Name - Enter the name of the new memory block.

    -

    Start Addr - Enter the start address of the new memory block.  If the - program language defines multiple address spaces, the address space must also be +

    Start Address - Enter the start address + (or Address Expression) + of the new memory block.  If + the program language defines multiple address spaces, the address space must also be specified.  The address space selection will not appear if only one is defined. If creating an overlay memory block within an existing overlay address space that space should be selected. A block within the default address space may not span across @@ -378,11 +380,14 @@

    Length - Length of the memory block to be moved (not editable).

    -

    New Start Address - Enter the NEW starting address for the block.  +

    New Start Address - Enter the new starting address + (or Address Expression) + for the block.  The NEW ending address will be computed.

    -

    New End Address -  Enter the NEW ending address for the block. The - NEW starting address will be computed.

    +

    New End Address -  Enter the new ending address + (or Address Expression) + for the block. The NEW starting address will be computed.

    You cannot move a block under the following conditions:

    @@ -404,11 +409,15 @@ enter the split point:

      -
    • Enter an end address for the first block (block to split), or
    • +
    • Enter an end address + (or Address Expression) + for the first block (block to split), or
    • Enter a length for the first block (block to split), or
    • -
    • Enter a start address for the second block (new block), or
    • +
    • Enter a start address + (or Address Expression) + for the second block (new block), or
    • Enter a length for the second block (new block).
    @@ -482,7 +491,9 @@
    -

    New Start Address - A new start address can be entered here.  It must +

    New Start Address - A new start address + (or Address Expression) + can be entered here.  It must be before the current start address.

    End Address - Displays the end address of the block (not editable).

    @@ -521,7 +532,9 @@

    Start Address - Displays the start address of the block (not editable).

    -

    New End Address - A new end address can be entered here. It must be after +

    New End Address - A new end address + (or Address Expression) + can be entered here. It must be after the current end address.

    Block Length - Displays the length of the block.  A new value can be diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMappedBlock.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMappedBlock.png index a5fd31ff6f..16ae13088e 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMappedBlock.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMappedBlock.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMemoryBlock.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMemoryBlock.png index ac077c9b3a..224b8e3e34 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMemoryBlock.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/AddMemoryBlock.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandDown.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandDown.png index 03b7fd38bb..ea150d2b96 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandDown.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandDown.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandUp.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandUp.png index 34af1c9895..c2ac66b8c9 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandUp.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MemoryExpandUp.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png index 8b74e22745..bf19daa7e3 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png index 34c1ad8587..8091b723bd 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png index f6cf4bff4e..7fa0b6a81a 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png and b/Ghidra/Features/Base/src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Misc/AddressExpressions.htm b/Ghidra/Features/Base/src/main/help/help/topics/Misc/AddressExpressions.htm new file mode 100644 index 0000000000..31664e9ddb --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/Misc/AddressExpressions.htm @@ -0,0 +1,175 @@ + + + + + + + Address Expressions + + + + +

    Address Expressions
    +

    + +
    +

    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.

    + +

    Operands

    + +
    +

    Operands can be either a number or a name that evaluates to an address.

    + +

    Names

    + +
    +

    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.

    + +

    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.

    +
    + +

    Numbers

    + +
    +

    Numeric operands will be evaluated as either a hex number or decimal number, depending + on the Hex/Decimal Mode of the input field.

    + +

    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".

    + +

    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 <CTRL> M.

    + +

    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.

    +
    +
    + +

    Operators

    + +
    +

    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.

    + +

    Operator precedence is the standard precedence defined by the "C" programming + language.

    + +

    Math Operators

    + +
    +

    Supported math operators are "+ - * /". These operators generate either a number or + address result depending on the operands.

    +
    + +

    Logical and Relational Operators

    + +
    +

    Supported logical operators are "< > <= >= == != || && and !". + These operators generator a numeric value of 0 or 1.

    +
    + +

    Bit Operators

    + +
    +

    Supported bit operators are <<, >>, &, |, ^, and ~. These operators + generate either a number or address result depending on the operands.

    +
    + +

    Groups Operators

    + +
    +

    Parenthesis can be used to group sub-expressions to control the order of + operations

    +
    +
    + +

    Result

    + +
    +

    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)

    +
    + +

    Examples

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Expression
    +
    Result
    +
    ENTRY+10Address 0x10 higher than the symbol "ENTRY"
    10+ENTRYError (Can't add an address to a number)
    100000+30Address 0x100030 (hex mode)
    0x100000+30Address 0x100030 (hex mode)
    0x100000+30Address 0x10001e (decimal mode)
    0x100000+(2*10)Address 0x100020
    ENTRY + 1<<4Address that is 16 higher than the symbol "ENTRY"
    X - (X > 100) * 100If symbol "X" address > 100, Result is 100 less than X; Otherwise X
    ENTRY | FFAddress that is the symbol "ENTRY" with its last 8 bits set to FF
    +
    +
    + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/CreateOffsetReferencesTable.htm b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/CreateOffsetReferencesTable.htm index 0b57f92378..5306ccf774 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/CreateOffsetReferencesTable.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/CreateOffsetReferencesTable.htm @@ -49,7 +49,9 @@
  • 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.
  • + selection. You can enter a different address + (or Address Expression) + as the base address.
  • The "Select Data Size" combo box has an entry for sizes 1, 2, 4, and 8. The size you select diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/References_from.htm b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/References_from.htm index ceef476d3c..2016ff571e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/References_from.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/References_from.htm @@ -1167,9 +1167,10 @@ c

    [ Offset] The To Address entry is required for normal memory references and specifies the reference destination as a memory offset within a selected address - space.  The address offset entry is always - interpretted as a unsigned hex value  (i.e., the "0x" entry prefix is assumed). -  For those processors with multiple address-spaces, a pull-down is also provided + space. Enter an address + (or Address Expression) to specify + the referenced address. + For those processors with multiple address-spaces, a pull-down is also provided allowing the address-space to be selected. Address spaces which overlay the OTHER non-loaded space are only included if the Include OTHER overlay spaces checkbox is selected.
    diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm index 365d8cbc20..2935b5eec7 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm @@ -124,7 +124,10 @@ range is smaller, then value associations for address range that was trucated is effectively cleared. For example, in the dialog shown above, if you change the end address to 01001b47 and change the value to 111, then addresses 01001b33 to 01001b47 - will have the value 111 and address 01001b48 will have no value.
    + 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 + Address Expression. +

    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughDialog.java index 3d58ff0476..60e31073fb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/fallthrough/FallThroughDialog.java @@ -4,9 +4,9 @@ * 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. @@ -117,15 +117,14 @@ class FallThroughDialog extends DialogComponentProvider implements ChangeListene } } - private void addressChanged() { + private void addressChanged(Address address) { if (changing) { return; } Runnable r = () -> { - Address addr = addrField.getAddress(); - if (addr != null || addrField.getValue().length() == 0) { - model.setCurrentFallthrough(addr); + if (address != null || addrField.getText().length() == 0) { + model.setCurrentFallthrough(address); } else { setStatusText("Invalid Address"); @@ -139,9 +138,7 @@ class FallThroughDialog extends DialogComponentProvider implements ChangeListene private JPanel create() { JPanel panel = new JPanel(new BorderLayout(0, 10)); panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); - addrField = new AddressInput(); - addrField.setAddressFactory(model.getProgram().getAddressFactory()); - addrField.addChangeListener(e -> addressChanged()); + addrField = new AddressInput(model.getProgram(), this::addressChanged); addrField.addActionListener(e -> model.setCurrentFallthrough(addrField.getAddress())); panel.add(createHomePanel(), BorderLayout.NORTH); panel.add(createAddressPanel(), BorderLayout.CENTER); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/VarnodeLocationCellEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/VarnodeLocationCellEditor.java index fcd29e0255..89bcb26a78 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/VarnodeLocationCellEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/VarnodeLocationCellEditor.java @@ -4,9 +4,9 @@ * 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. @@ -148,8 +148,9 @@ class VarnodeLocationCellEditor extends AbstractCellEditor } private Component createAddressEditor(VarnodeInfo varnode) { - addressInput = new AddressInput(BorderFactory.createEmptyBorder()); - addressInput.setAddressFactory(program.getAddressFactory()); + addressInput = new AddressInput(program); + addressInput.setComponentBorders(BorderFactory.createEmptyBorder()); + Address address = varnode.getAddress(); if (address != null) { addressInput.setAddress(address); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockDialog.java index f14e991a1a..e08091b5ef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockDialog.java @@ -4,9 +4,9 @@ * 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. @@ -35,7 +35,6 @@ import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.mem.FileBytes; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlockType; @@ -69,7 +68,6 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { private JCheckBox overlayCB; private RegisterField initialValueField; private JLabel initialValueLabel; - private AddressFactory addrFactory; private AddressInput baseAddrField; // used for Bit and Byte mapped blocks private IntegerTextField schemeDestByteCountField; // used for Byte mapped blocks private IntegerTextField schemeSrcByteCountField; // used for Byte mapped blocks @@ -134,12 +132,12 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { } private Component buildBasicInfoPanel() { - JPanel panel = new JPanel(new PairLayout(4, 10, 150)); + JPanel panel = new JPanel(new PairLayout(5, 10, 150)); panel.setBorder(BorderFactory.createEmptyBorder(5, 7, 4, 5)); panel.add(new GLabel("Block Name:", SwingConstants.RIGHT)); panel.add(buildNameField()); - panel.add(new GLabel("Start Addr:", SwingConstants.RIGHT)); + panel.add(new GLabel("Start Address:", SwingConstants.RIGHT)); panel.add(buildAddressField()); panel.add(new GLabel("Length:", SwingConstants.RIGHT)); panel.add(buildLengthField()); @@ -422,26 +420,16 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { model.setFileBytes((FileBytes) fileBytesComboBox.getSelectedItem()); } - private void addrChanged() { - Address addr = null; - try { - addr = addrField.getAddress(); - } - catch (IllegalArgumentException e) { - // just let it be null - } - model.setStartAddress(addr); + private void addressChanged(Address address) { + model.setStartAddress(address); } - private void baseAddressChanged() { - Address addr = null; - try { - addr = baseAddrField.getAddress(); - } - catch (IllegalArgumentException e) { - // just let it be null - } - model.setBaseAddress(addr); + private void addressError(String errorMessage) { + model.setAddressError(errorMessage); + } + + private void baseAddressChanged(Address address) { + model.setBaseAddress(address); } private void schemeSrcByteCountChanged() { @@ -476,12 +464,11 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { } private JPanel buildMappedPanel() { + Program program = model.getProgram(); JPanel panel = new JPanel(new PairLayout()); - baseAddrField = new AddressInput(); - baseAddrField.setAddressFactory(addrFactory); + baseAddrField = new AddressInput(program, this::baseAddressChanged); baseAddrField.setName("Source Addr"); - baseAddrField.addChangeListener(ev -> baseAddressChanged()); baseAddrField.setAccessibleName("Source Address"); JPanel schemePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); @@ -504,7 +491,6 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { schemePanel.add(new GLabel(" : ")); schemePanel.add(schemeSrcByteCountField.getComponent()); - Program program = model.getProgram(); Address minAddr = program.getMinAddress(); if (minAddr == null) { minAddr = program.getAddressFactory().getDefaultAddressSpace().getAddress(0); @@ -561,12 +547,12 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener { } private Component buildAddressField() { - addrField = new AddressInput(); + Program program = model.getProgram(); + addrField = new AddressInput(program, this::addressChanged); + addrField.setAddressErrorConsumer(this::addressError); + addrField.setAddressSpaceFilter(AddressInput.ALL_MEMORY_SPACES); addrField.setName("Start Addr"); addrField.setAccessibleName("Memory Block Start Address"); - addrFactory = model.getProgram().getAddressFactory(); - addrField.setAddressFactory(addrFactory, AddressInput.INCLUDE_ALL_MEMORY_SPACES); - addrField.addChangeListener(ev -> addrChanged()); return addrField; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockModel.java index ecb9be0f45..0db19ece94 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/AddBlockModel.java @@ -4,9 +4,9 @@ * 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. @@ -59,6 +59,7 @@ class AddBlockModel { private String comment; private FileBytes fileBytes; private long fileBytesOffset = -1; + private String addressErrorMessage; enum InitializedType { UNINITIALIZED, INITIALIZED_FROM_VALUE, INITIALIZED_FROM_FILE_BYTES; @@ -90,6 +91,14 @@ class AddBlockModel { void setStartAddress(Address addr) { startAddr = addr; + addressErrorMessage = null; + validateInfo(); + listener.stateChanged(null); + } + + void setAddressError(String errorMessage) { + startAddr = null; + addressErrorMessage = errorMessage; validateInfo(); listener.stateChanged(null); } @@ -432,6 +441,9 @@ class AddBlockModel { return true; } message = "Please enter a valid Start Address"; + if (addressErrorMessage != null) { + message = "Invalid Address: " + addressErrorMessage; + } return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/ExpandBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/ExpandBlockDialog.java index 1f6e981e22..0d380004b7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/ExpandBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/ExpandBlockDialog.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.memory; import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.event.ActionListener; +import java.util.function.Consumer; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -30,7 +31,7 @@ import ghidra.app.util.AddressInput; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; import ghidra.util.HelpLocation; import ghidra.util.layout.PairLayout; @@ -49,7 +50,6 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene private final static String EXPAND_UP_TITLE = "Expand Block Up"; private final static String EXPAND_DOWN_TITLE = "Expand Block Down"; private int dialogType; - private AddressFactory addrFactory; private AddressInput startAddressInput; private AddressInput endAddressInput; private JTextField startField; @@ -66,7 +66,7 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene * @param af * @param dialogType */ - ExpandBlockDialog(PluginTool tool, ExpandBlockModel model, MemoryBlock block, AddressFactory af, + ExpandBlockDialog(PluginTool tool, ExpandBlockModel model, MemoryBlock block, Program program, int dialogType) { super(dialogType == EXPAND_UP ? EXPAND_UP_TITLE : EXPAND_DOWN_TITLE, true); this.tool = tool; @@ -74,9 +74,8 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene this.dialogType = dialogType; setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP, dialogType == EXPAND_UP ? EXPAND_UP_TITLE : EXPAND_DOWN_TITLE)); - addrFactory = af; model.setChangeListener(this); - addWorkPanel(create(block)); + addWorkPanel(create(block, program)); addOKButton(); addCancelButton(); setOkEnabled(false); @@ -109,16 +108,16 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene * Create the main work panel. * @return JPanel */ - private JPanel create(MemoryBlock block) { + private JPanel create(MemoryBlock block, Program program) { JPanel panel = new JPanel(new PairLayout(5, 5, 150)); - startAddressInput = new AddressInput(); + AddressChangeListener listener = new AddressChangeListener(); + + startAddressInput = new AddressInput(program, listener); startAddressInput.setName("NewStartAddress"); - startAddressInput.setAddressFactory(addrFactory); startAddressInput.setAccessibleName("New Start Address"); - endAddressInput = new AddressInput(); + endAddressInput = new AddressInput(program, listener); endAddressInput.setName("EndAddress"); - endAddressInput.setAddressFactory(addrFactory); endAddressInput.setAccessibleName("New End Address"); Address start = block.getStart(); @@ -161,9 +160,6 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene } private void addListeners() { - - startAddressInput.addChangeListener(new AddressChangeListener()); - endAddressInput.addChangeListener(new AddressChangeListener()); lengthField.setChangeListener(new LengthChangeListener()); ActionListener al = e -> setStatusText(""); @@ -206,10 +202,10 @@ class ExpandBlockDialog extends DialogComponentProvider implements ChangeListene * Listener on the AddressInput field; update length field when the * address input field changes. */ - private class AddressChangeListener implements ChangeListener { + private class AddressChangeListener implements Consumer
    { @Override - public void stateChanged(ChangeEvent event) { + public void accept(Address address) { if (isChanging) { return; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java index dfdadc9024..f76b429a55 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java @@ -690,7 +690,7 @@ class MemoryMapProvider extends ComponentProviderAdapter { "OTHER overlay blocks can not be split."); } else { - SplitBlockDialog d = new SplitBlockDialog(plugin, block, program.getAddressFactory()); + SplitBlockDialog d = new SplitBlockDialog(plugin, block, program); tool.showDialog(d, this); } } @@ -711,7 +711,7 @@ class MemoryMapProvider extends ComponentProviderAdapter { } ExpandBlockDialog dialog = - new ExpandBlockDialog(tool, model, block, program.getAddressFactory(), dialogType); + new ExpandBlockDialog(tool, model, block, program, dialogType); model.initialize(block); dialog.dispose(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java index d258e42662..b2767dab70 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java @@ -4,9 +4,9 @@ * 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. @@ -28,7 +28,7 @@ import ghidra.app.util.AddressInput; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.Swing; import ghidra.util.layout.PairLayout; @@ -82,9 +82,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc setOkEnabled(false); changing = true; if (!isVisible()) { - AddressFactory factory = model.getAddressFactory(); - newStartField.setAddressFactory(factory); - newEndField.setAddressFactory(factory); + Program program = model.getProgram(); + newStartField.setProgram(program); + newEndField.setProgram(program); } Address newStart = model.getNewStartAddress(); if (newStart != null) { @@ -136,8 +136,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc } private JPanel buildMainPanel() { - JPanel panel = new JPanel(new PairLayout(5, 20, 150)); - panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + JPanel panel = new JPanel(new PairLayout(2, 10, 150)); + panel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20)); blockNameLabel = new GDLabel(".text"); blockNameLabel.setName("blockName"); // name components for junits @@ -150,15 +150,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc lengthLabel = new GDLabel("4096 (0x1000)"); lengthLabel.setName("length"); - newStartField = new AddressInput(); + newStartField = new AddressInput(model.getProgram(), this::startChanged); newStartField.setName("newStart"); - newEndField = new AddressInput(); + newEndField = new AddressInput(model.getProgram(), this::endChanged); newEndField.setName("newEnd"); - newStartField.addChangeListener(e -> startChanged()); - newEndField.addChangeListener(e -> endChanged()); - panel.add(new GLabel("Name:", SwingConstants.RIGHT)); panel.add(blockNameLabel); panel.add(new GLabel("Start Address:", SwingConstants.RIGHT)); @@ -174,13 +171,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc return panel; } - private void startChanged() { + private void startChanged(Address address) { if (changing) { return; } - Address newStart = newStartField.getAddress(); - if (newStart != null) { - model.setNewStartAddress(newStart); + if (address != null) { + model.setNewStartAddress(address); } else { setStatusText("Invalid Address"); @@ -188,13 +184,12 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc } } - private void endChanged() { + private void endChanged(Address address) { if (changing) { return; } - Address newEnd = newEndField.getAddress(); - if (newEnd != null) { - model.setNewEndAddress(newEnd); + if (address != null) { + model.setNewEndAddress(address); } else { setStatusText("Invalid Address"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockModel.java index 0bfed6c5c6..d9d2a75580 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockModel.java @@ -4,9 +4,9 @@ * 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. @@ -18,7 +18,8 @@ package ghidra.app.plugin.core.memory; import ghidra.app.cmd.memory.MoveBlockListener; import ghidra.app.cmd.memory.MoveBlockTask; import ghidra.framework.model.*; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; @@ -183,8 +184,8 @@ class MoveBlockModel implements DomainObjectListener { program = null; } - AddressFactory getAddressFactory() { - return program.getAddressFactory(); + Program getProgram() { + return program; } private Address getEndAddress(Address start) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/SplitBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/SplitBlockDialog.java index 667443eb2b..d3833417b8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/SplitBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/SplitBlockDialog.java @@ -4,9 +4,9 @@ * 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. @@ -27,7 +27,9 @@ import docking.widgets.label.GLabel; import ghidra.app.plugin.core.misc.RegisterField; import ghidra.app.util.AddressInput; import ghidra.app.util.HelpTopics; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; import ghidra.util.HelpLocation; @@ -50,16 +52,14 @@ class SplitBlockDialog extends DialogComponentProvider { private JTextField blockTwoEndField; private RegisterField blockTwoLengthField; private MemoryBlock block; - private AddressFactory addrFactory; private MemoryMapPlugin plugin; - SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, AddressFactory af) { + SplitBlockDialog(MemoryMapPlugin plugin, MemoryBlock block, Program program) { super("Split Block"); this.plugin = plugin; this.block = block; - addrFactory = af; setHelpLocation(new HelpLocation(HelpTopics.MEMORY_MAP, "Split Block")); - addWorkPanel(create()); + addWorkPanel(create(program)); addOKButton(); addCancelButton(); setOkEnabled(false); @@ -89,8 +89,8 @@ class SplitBlockDialog extends DialogComponentProvider { * Create the work panel. * @return JPanel */ - private JPanel create() { - JPanel panelOne = new JPanel(new PairLayout(5, 5, 150)); + private JPanel create(Program program) { + JPanel panelOne = new JPanel(new PairLayout(5, 10, 150)); panelOne.setBorder(BorderFactory.createTitledBorder("Block to Split")); blockOneNameField = new JTextField(10); blockOneNameField.setName("BlockOneName"); @@ -99,7 +99,7 @@ class SplitBlockDialog extends DialogComponentProvider { blockOneStartField.setName("BlockOneStart"); blockOneStartField.getAccessibleContext().setAccessibleName("Address of Block To Split"); - blockOneEnd = new AddressInput(); + blockOneEnd = new AddressInput(program, this::blockOneEndChanged); blockOneEnd.setName("BlockOneEnd"); blockOneEnd.setAccessibleName("New Block End Adddress"); @@ -121,7 +121,7 @@ class SplitBlockDialog extends DialogComponentProvider { blockTwoNameField = new JTextField(10); blockTwoNameField.setName("BlockTwoName"); blockTwoNameField.getAccessibleContext().setAccessibleName("Name of New Block"); - blockTwoStart = new AddressInput(); + blockTwoStart = new AddressInput(program, this::blockTwoStartChanged); blockTwoStart.setName("BlockTwoStart"); blockTwoStart.setAccessibleName("New Block Start Address"); blockTwoEndField = new JTextField(10); @@ -141,11 +141,11 @@ class SplitBlockDialog extends DialogComponentProvider { panelTwo.add(blockTwoLengthField); JPanel mainPanel = new JPanel(); + mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20)); BoxLayout bl = new BoxLayout(mainPanel, BoxLayout.Y_AXIS); mainPanel.setLayout(bl); - mainPanel.add(Box.createVerticalStrut(5)); mainPanel.add(panelOne); - mainPanel.add(Box.createVerticalStrut(10)); + mainPanel.add(Box.createVerticalStrut(20)); mainPanel.add(panelTwo); return mainPanel; @@ -166,7 +166,6 @@ class SplitBlockDialog extends DialogComponentProvider { blockOneStartField.setText(startAddr.toString()); blockOneStartField.setEnabled(false); - blockOneEnd.setAddressFactory(addrFactory); blockOneEnd.setAddress(endAddr); blockOneEnd.setAddressSpaceEditable(false); @@ -174,7 +173,6 @@ class SplitBlockDialog extends DialogComponentProvider { blockTwoNameField.setText(name + ".split"); - blockTwoStart.setAddressFactory(addrFactory); blockTwoStart.setAddress(startAddr); blockTwoStart.setAddressSpaceEditable(false); @@ -189,8 +187,6 @@ class SplitBlockDialog extends DialogComponentProvider { blockOneLengthField.setChangeListener(new LengthChangeListener(blockOneLengthField)); blockTwoLengthField.setChangeListener(new LengthChangeListener(blockTwoLengthField)); - blockOneEnd.addChangeListener(new AddressChangeListener(blockOneEnd)); - blockTwoStart.addChangeListener(new AddressChangeListener(blockTwoStart)); ActionListener al = e -> setStatusText(""); blockOneLengthField.addActionListener(al); @@ -307,135 +303,101 @@ class SplitBlockDialog extends DialogComponentProvider { } } - /** - * Listener on the AddressInput fields; update other fields when either - * of these fields change. - */ - private class AddressChangeListener implements ChangeListener { + private void blockOneEndChanged(Address end) { + setStatusText(""); + boolean isValid = checkBlockOneEndAddress(end); + setOkEnabled(isValid); + } - AddressInput source; + private void blockTwoStartChanged(Address start) { + setStatusText(""); + boolean isValid = checkBlockTwoStartChanged(start); + setOkEnabled(isValid); + } - public AddressChangeListener(AddressInput source) { - this.source = source; + private boolean checkBlockOneEndAddress(Address end) { + + if (end == null) { + setStatusText("Invalid Address"); + return false; } - @Override - public void stateChanged(ChangeEvent event) { - setStatusText(""); - boolean ok = false; - if (source == blockOneEnd) { - ok = blockOneEndChanged(); - } - else if (source == blockTwoStart) { - ok = blockTwoStartChanged(); - } - setOkEnabled(ok); + Address start = block.getStart(); + + if (end.compareTo(start) < 0) { + setStatusText("End address must be greater than start"); + return false; } - - private Address getAddress() throws InvalidInputException { - - AddressInput field = source; - Address addr = field.getAddress(); - if (addr == null && field.hasInput()) { - throw new InvalidInputException(); - } - return addr; + if (end.compareTo(block.getEnd()) == 0) { + return false; } - - private boolean blockOneEndChanged() { - Address start = block.getStart(); - Address end = null; - try { - end = getAddress(); - } - catch (InvalidInputException e) { - setStatusText("Invalid Address"); - return false; - } - - if (end == null) { - return false; - } - if (end.compareTo(start) < 0) { - setStatusText("End address must be greater than start"); - return false; - } - if (end.compareTo(block.getEnd()) == 0) { - return false; - } - // change block One length and blockTwoStart, blockTwoLength - long length = 0; - try { - length = end.subtract(start) + 1; - } - catch (IllegalArgumentException e) { - setStatusText(e.getMessage()); - return false; - } - long blockSize = block.getSize(); - if (length > blockSize) { - setStatusText( - "End address must be less than original block end (" + block.getEnd() + ")"); - return false; - } - blockOneLengthField.setValue(Long.valueOf(length)); - - try { - Address b2Start = end.addNoWrap(1); - blockTwoStart.setAddress(b2Start); - length = block.getEnd().subtract(b2Start) + 1; - blockTwoLengthField.setValue(Long.valueOf(length)); - } - catch (Exception e) { - if (e instanceof AddressOverflowException) { - setStatusText("Could not create new start address"); - } - return false; - } - return true; + // change block One length and blockTwoStart, blockTwoLength + long length = 0; + try { + length = end.subtract(start) + 1; } + catch (IllegalArgumentException e) { + setStatusText(e.getMessage()); + return false; + } + long blockSize = block.getSize(); + if (length > blockSize) { + setStatusText( + "End address must be less than original block end (" + block.getEnd() + ")"); + return false; + } + blockOneLengthField.setValue(Long.valueOf(length)); - private boolean blockTwoStartChanged() { - Address start = null; - try { - start = getAddress(); - } - catch (InvalidInputException e) { - setStatusText("Invalid Address"); - return false; - } - Address end = block.getEnd(); - if (start == null) { - return false; - } - else if (start.compareTo(end) > 0) { - setStatusText("Start address must not be greater than end"); - return false; - } - else if (start.compareTo(block.getStart()) <= 0) { - setStatusText("Start address must be greater than original block start (" + - block.getStart() + ")"); - return false; - } - - // change block Two length, blockOneEnd, block One length - long length = end.subtract(start) + 1; + try { + Address b2Start = end.addNoWrap(1); + blockTwoStart.setAddress(b2Start); + length = block.getEnd().subtract(b2Start) + 1; blockTwoLengthField.setValue(Long.valueOf(length)); - try { - Address b1End = start.subtractNoWrap(1); - blockOneEnd.setAddress(b1End); - length = b1End.subtract(block.getStart()) + 1; - blockOneLengthField.setValue(Long.valueOf(length)); - } - catch (Exception e) { - if (e instanceof AddressOverflowException) { - setStatusText("Could not create end address for split block"); - } - return false; - } - return true; } + catch (Exception e) { + if (e instanceof AddressOverflowException) { + setStatusText("Could not create new start address"); + } + return false; + } + return true; } + private boolean checkBlockTwoStartChanged(Address start) { + if (start == null) { + setStatusText("Invalid Address"); + return false; + } + Address end = block.getEnd(); + if (start == null) { + return false; + } + else if (start.compareTo(end) > 0) { + setStatusText("Start address must not be greater than end"); + return false; + } + else if (start.compareTo(block.getStart()) <= 0) { + setStatusText("Start address must be greater than original block start (" + + block.getStart() + ")"); + return false; + } + + // change block Two length, blockOneEnd, block One length + long length = end.subtract(start) + 1; + blockTwoLengthField.setValue(Long.valueOf(length)); + try { + Address b1End = start.subtractNoWrap(1); + blockOneEnd.setAddress(b1End); + length = b1End.subtract(block.getStart()) + 1; + blockOneLengthField.setValue(Long.valueOf(length)); + } + catch (Exception e) { + if (e instanceof AddressOverflowException) { + setStatusText("Could not create end address for split block"); + } + return false; + } + return true; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditExternalReferencePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditExternalReferencePanel.java index d035cdd865..8682ea8bfa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditExternalReferencePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditExternalReferencePanel.java @@ -4,9 +4,9 @@ * 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. @@ -231,7 +231,7 @@ class EditExternalReferencePanel extends EditReferencePanel { updateExtLibPath(); extLabel.setText(extLoc.getLabel()); - extAddr.setAddressFactory(program.getAddressFactory()); + extAddr.setProgram(program); Address addr = extLoc.getAddress(); if (addr != null) { extAddr.setAddress(addr); @@ -260,7 +260,7 @@ class EditExternalReferencePanel extends EditReferencePanel { extLibPath.setText(null); extLabel.setText(null); - extAddr.setAddressFactory(program.getAddressFactory()); + extAddr.setProgram(program); extAddr.clear(); extLibName.requestFocus(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java index a390606c53..875d13ad8e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java @@ -191,7 +191,8 @@ class EditMemoryReferencePanel extends EditReferencePanel { } private void initializeToAddressField(Address toAddr) { - toAddressField.setAddressFactory(fromCodeUnit.getProgram().getAddressFactory(), (s) -> { + Program program = fromCodeUnit.getProgram(); + toAddressField.setProgram(program, (s) -> { if (s.isLoadedMemorySpace()) { return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTableDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTableDialog.java index 1ce2c350bb..b97ce8479f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTableDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTableDialog.java @@ -4,9 +4,9 @@ * 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. @@ -19,8 +19,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import docking.DialogComponentProvider; import docking.widgets.checkbox.GCheckBox; @@ -30,7 +28,7 @@ import ghidra.app.util.AddressInput; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.exception.CancelledException; import ghidra.util.layout.PairLayout; @@ -44,7 +42,6 @@ import ghidra.util.layout.PairLayout; public class OffsetTableDialog extends DialogComponentProvider { private AddressInput addrInput; - private AddressFactory addrFactory; private JComboBox comboBox; private Address defaultAddress; private JCheckBox signedCheckBox; @@ -52,15 +49,13 @@ public class OffsetTableDialog extends DialogComponentProvider { /** * Construct a new dialog - * @param parent parent of this dialog * @param defaultAddress address to put in the address field as a default - * @param addrFactory address factory required by AddressInput object + * @param program the program */ - OffsetTableDialog(Address defaultAddress, AddressFactory addrFactory) { + OffsetTableDialog(Address defaultAddress, Program program) { super("Create Offset References", true); this.defaultAddress = defaultAddress; - this.addrFactory = addrFactory; - addWorkPanel(buildMainPanel()); + addWorkPanel(buildMainPanel(program)); addOKButton(); addCancelButton(); setHelpLocation(new HelpLocation(HelpTopics.REFERENCES, "Create_Offset_References")); @@ -128,23 +123,16 @@ public class OffsetTableDialog extends DialogComponentProvider { signedCheckBox.setSelected(isSigned); } - private JPanel buildMainPanel() { + private JPanel buildMainPanel(Program program) { JPanel panel = new JPanel(new PairLayout(10, 5)); panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20)); - addrInput = new AddressInput(); + addrInput = new AddressInput(program, a -> clearStatusText()); addrInput.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { okCallback(); } }); - addrInput.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - clearStatusText(); - } - }); - addrInput.setAddressFactory(addrFactory); addrInput.setAddress(defaultAddress); panel.add(new GLabel("Enter Base Address:", SwingConstants.RIGHT)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTablePlugin.java index cb71a01366..ad2faf2d17 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/OffsetTablePlugin.java @@ -4,9 +4,9 @@ * 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. @@ -101,10 +101,10 @@ public class OffsetTablePlugin extends Plugin { true); return; } - AddressFactory addressFactory = context.getProgram().getAddressFactory(); + Program program = context.getProgram(); Address minAddress = context.getSelection().getMinAddress(); - OffsetTableDialog dialog = new OffsetTableDialog(minAddress, addressFactory); + OffsetTableDialog dialog = new OffsetTableDialog(minAddress, program); dialog.setSelectedSize(lastSelectedSize); dialog.setSigned(lastSigned); try { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java index a21fb4e75f..7690205dc0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java @@ -4,9 +4,9 @@ * 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. @@ -16,17 +16,18 @@ package ghidra.app.plugin.core.register; import java.math.BigInteger; +import java.util.function.Consumer; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import docking.DialogComponentProvider; import docking.widgets.label.GLabel; import ghidra.app.util.AddressInput; import ghidra.app.util.bean.FixedBitSizeValueField; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.MessageType; import ghidra.util.layout.PairLayout; @@ -39,9 +40,9 @@ class EditRegisterValueDialog extends DialogComponentProvider { private boolean wasCancelled = true; EditRegisterValueDialog(Register register, Address start, Address end, BigInteger value, - AddressFactory factory) { + Program program) { super("Edit Register Value Range"); - addWorkPanel(buildWorkPanel(register, start, end, value, factory)); + addWorkPanel(buildWorkPanel(register, start, end, value, program)); addOKButton(); addCancelButton(); @@ -49,24 +50,15 @@ class EditRegisterValueDialog extends DialogComponentProvider { } private JComponent buildWorkPanel(Register register, Address start, Address end, - BigInteger value, AddressFactory factory) { + BigInteger value, Program program) { JTextField registerField = new JTextField(register.getName() + " (" + register.getBitLength() + ")"); registerField.setEditable(false); - startAddrField = new AddressInput(); - endAddrField = new AddressInput(); - ChangeListener changeListener = new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateOk(); - } - }; - startAddrField.setAddressFactory(factory); - endAddrField.setAddressFactory(factory); - startAddrField.addChangeListener(changeListener); - endAddrField.addChangeListener(changeListener); + Consumer
    addressChangeListener = a -> updateOk(); + startAddrField = new AddressInput(program, addressChangeListener); + endAddrField = new AddressInput(program, addressChangeListener); registerValueField = new FixedBitSizeValueField(register.getBitLength(), true, false); startAddrField.setAddress(start); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java index 2a998d17b1..ad13415478 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java @@ -4,9 +4,9 @@ * 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. @@ -89,7 +89,7 @@ class RegisterValuesPanel extends JPanel { Address end = range.getEndAddress(); BigInteger value = range.getValue(); EditRegisterValueDialog dialog = new EditRegisterValueDialog(selectedRegister, start, end, - value, currentProgram.getAddressFactory()); + value, currentProgram); tool.showDialog(dialog, this); if (!dialog.wasCancelled()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/EditExternalLocationPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/EditExternalLocationPanel.java index 1790bda493..368b92a8c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/EditExternalLocationPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/EditExternalLocationPanel.java @@ -4,9 +4,9 @@ * 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. @@ -300,7 +300,7 @@ class EditExternalLocationPanel extends JPanel { if (extOriginalLabelTextField != null) { extOriginalLabelTextField.setText(startingOriginalName); } - extAddressInputWidget.setAddressFactory(program.getAddressFactory()); + extAddressInputWidget.setProgram(program); if (startingLocationAddress != null) { extAddressInputWidget.setAddress(startingLocationAddress); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index add559fc85..bdc31cc6e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -2630,7 +2630,7 @@ public abstract class GhidraScript extends FlatProgramAPI { Address choice = doAsk(Integer.class, title, message, existingValue, lastValue -> { AskAddrDialog dialog = - new AskAddrDialog(title, message, currentProgram.getAddressFactory(), lastValue); + new AskAddrDialog(title, message, currentProgram, lastValue); if (dialog.isCanceled()) { throw new CancelledException(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java index c31a4318f0..f0b3014195 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java @@ -16,34 +16,44 @@ package ghidra.app.util; import java.awt.BorderLayout; -import java.awt.FontMetrics; +import java.awt.CardLayout; import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Comparator; +import java.util.function.Consumer; import java.util.function.Predicate; import javax.swing.*; import javax.swing.border.Border; -import javax.swing.event.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import docking.widgets.combobox.GComboBox; import docking.widgets.table.FocusableEditor; +import docking.widgets.textfield.HexDecimalModeTextField; +import generic.expressions.ExpressionException; import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.AddressEvaluator; +import utility.function.Dummy; /** - * Panel for user input of addresses. Handles case with multiple address - * spaces. + * Input field for entering address or address expression. Handles multiple address + * spaces and supports both hex and decimal number modes for evaluating numbers. */ public class AddressInput extends JPanel implements FocusableEditor { - private JTextField textField; - private JComboBox combo; + public final static Predicate ALL_MEMORY_SPACES = s -> s.isMemorySpace(); + public final static Predicate LOADED_MEMORY_SPACES = s -> s.isLoadedMemorySpace(); + + private HexDecimalModeTextField textField; + private AddressSpaceField addressSpaceField; + AddressEvaluator addressEvaluator; + private Predicate addressSpaceFilter = LOADED_MEMORY_SPACES; + private Consumer
    addressChangedConsumer; + private Consumer addressErrorConsumer = Dummy.consumer(); private boolean comboAdded; - private AddressFactory addrFactory; - private ChangeListener changeListener; - private boolean updatingAddress; - private boolean updateSpaceField; - private boolean stateChanging; - private JTextField spaceField; + private boolean assumeHex = true; + private boolean notificationsEnabled = true; private static final Comparator ADDRESS_SPACE_SORT_COMPARATOR = (s1, s2) -> { if (s1.isOverlaySpace()) { @@ -58,65 +68,132 @@ public class AddressInput extends JPanel implements FocusableEditor { }; /** - * Constructor for AddressInput. - * @param border border around each subcomponent (combo/text fields). + * Constructs an AddressInput field with no specified program or address. */ - public AddressInput(Border border) { - this(); - combo.setBorder(border); + public AddressInput() { + this(null, null, null); + } + + /** + * Constructs an AddressInput field with a consumer to be called when the address field's + * value changes. + * @param addressChangedConsumer the consumer to be called when the value in the address field + * changes + */ + public AddressInput(Consumer
    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
    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
    addressChangedConsumer) { + this(null, factory, addressChangedConsumer); + } + + private AddressInput(Program program, AddressFactory factory, + Consumer
    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 spaceFilter) { + this.addressSpaceFilter = spaceFilter; + updateAddressSpaceCombo(); + } + + /** + * Sets the text in the expression input textfield. + * @param text the text to initialize the input textfield + */ + public void setText(String text) { + textField.setText(text); + } + + /** + * Used to set the internal borders for use in specialized use cases such as a table field + * editor. + * @param border the border to use for the internal components that make up this input field + */ + public void setComponentBorders(Border border) { + addressSpaceField.setComponentsBorder(border); textField.setBorder(border); } /** - * Constructor for AddressInput. + * Sets the hex/decimal mode for this field. When in hex mode, all numbers are assumed to be + * hexadecimal values. When in decimal mode, all numbers are assumed to be decimal numbers + * unless prefixed with "0x". + * @param hexMode true to assume numbers are hexadecimal. */ - public AddressInput() { - - setLayout(new BorderLayout()); - textField = new JTextField(10); - textField.setName("JTextField");//for JUnits... - combo = new GComboBox<>(); - combo.setName("JComboBox");//for JUnits... - combo.getAccessibleContext().setAccessibleName("Address Space"); - add(textField, BorderLayout.CENTER); - //add(combo, BorderLayout.WEST); - comboAdded = false; - - textField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - stateChanged(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - stateChanged(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - stateChanged(); - } - }); - - combo.addActionListener(ev -> stateChanged()); + public void setAssumeHex(boolean hexMode) { + textField.setHexMode(hexMode); + hexModeChanged(hexMode); } /** * Set the field to display the given address + * @param address the new address to display */ - public void setAddress(Address addr) { - if (stateChanging) { - // called while we are in doing a state changed notification + public void setAddress(Address address) { + if (address.equals(getAddress())) { return; } - updatingAddress = true; - textField.setText(addr.toString(false)); - combo.setSelectedItem(addr.getAddressSpace()); - updatingAddress = false; - if (updateSpaceField) { - updateSpaceField = false; - spaceField.setText(addr.getAddressSpace().getName()); + notificationsEnabled = false; + try { + String addressString = address.toString(false); + addressString = removeLeadingZeros(addressString); + if (!assumeHex) { + addressString = "0x" + addressString; + } + textField.setText(addressString); + addressSpaceField.setAddressSpace(address.getAddressSpace()); + } + finally { + notificationsEnabled = true; } } @@ -124,42 +201,53 @@ public class AddressInput extends JPanel implements FocusableEditor { * Returns the address in the field or null if the address can't * be parsed. * @return The address for the current value in the text field + * @throws ExpressionException if expression can not be evaluated to a valid address. * * @throws NullPointerException if AddressFactory has not been set. */ - public Address getAddress() { - String addrStr = textField.getText(); - - AddressSpace space = getAddressSpace(); - try { - return space.getAddress(addrStr); + public Address getAddressWithExceptions() throws ExpressionException { + String addrExpression = textField.getText(); + if (addrExpression.isBlank()) { + return null; } - catch (AddressFormatException e) { + + return addressEvaluator.parseAsAddress(addrExpression); + } + + /** + * Gets the current address the field evaluates to or null if the text does not evaluate to + * a valid, unique address. + * @return the current address the field evalutes to or null if the text does not evalute to + * a valid unique address. + */ + public Address getAddress() { + try { + return getAddressWithExceptions(); + } + catch (ExpressionException e) { return null; } } /** - * Returns the address space selected in the combobox or in the input text itself - * if specified (eg: "register:1"). If the address space is not specified; returns the - * default space. + * Returns the address space selected in the combobox the default address space if the + * comboBox is not being shown. * - * @throws NullPointerException if AddressFactory has not been set. + * @return the selected address space, or the default address space if no combo added, or + * null if no program is set. */ public AddressSpace getAddressSpace() { - if (comboAdded) { - return (AddressSpace) combo.getSelectedItem(); - } - return addrFactory.getDefaultAddressSpace(); + return addressSpaceField.getAddressSpace(); } /** * Returns true if the Address input field contains text. * The getAddress() method will return null if text is not * a valid address. + * @return true if the address field is not blank */ public boolean hasInput() { - return textField.getText().length() != 0; + return !textField.getText().isBlank(); } /** @@ -167,96 +255,48 @@ public class AddressInput extends JPanel implements FocusableEditor { * @return the text in this field */ public String getText() { - return textField.getText(); - } - - public AddressFactory getAddressFactory() { - return addrFactory; + return textField.getText().trim(); } /** - * Address Space predicate which includes all loaded memory spaces. - * See {@link AddressSpace#isLoadedMemorySpace()}. - * Intended for use with {@link #setAddressFactory(AddressFactory, Predicate)}. - */ - public final static Predicate 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 INCLUDE_ALL_MEMORY_SPACES = (s) -> { - return s.isMemorySpace(); - }; - - /** - * Set the address factory to be used to parse addresses. Also - * used to set the combo box with the list of valid address spaces - * if there is more than one space. Only loaded memory spaces + * Set the program to be used to parse addresses and expressions and also + * to determine the list of valid address spaces. Only loaded memory spaces * will be allowed (see {@link AddressSpace#isLoadedMemorySpace()}). - * @param factory address factory to use + * @param program the program to use to resolve address expressions + */ + public void setProgram(Program program) { + addressEvaluator = new AddressEvaluator(program, assumeHex); + updateAddressSpaceCombo(); + } + + /** + * Sets the program and the address space filter at the same time. This avoid some weird + * intermediate results if the are set separately. + * @param program the program to use to parse addresses and expressions. + * @param addessSpaceFilter the predicate to determine which address spaces are user selectable + */ + public void setProgram(Program program, Predicate addessSpaceFilter) { + this.addressSpaceFilter = addessSpaceFilter; + setProgram(program); + } + + /** + * Legacy method for setting the address factory to be used to parse address. Should only be + * used when a program is not readily available. + * @param factory the address factory to be used to parse addresses. */ public void setAddressFactory(AddressFactory factory) { - setAddressFactory(factory, INCLUDE_LOADED_MEMORY_SPACES); + addressEvaluator = new AddressEvaluator(factory, assumeHex); + updateAddressSpaceCombo(); } /** - * Set the address factory to be used to parse addresses. Also used to set the combo box - * with the list of valid address spaces if there is more than one space. The specified - * predicate will be used to determine if an address space should be included. - * @param factory address factory to use - * @param predicate callback used to determine if an address space should be included for selection + * Sets a consumer to be notified when the address input field changes, but can't be parsed + * into a valid address. + * @param addressErrorConsumer the consumer to be notified for bad address input */ - public void setAddressFactory(AddressFactory factory, Predicate predicate) { - this.addrFactory = factory; - AddressSpace[] spaces = factory.getAddressSpaces(); - - Arrays.sort(spaces, ADDRESS_SPACE_SORT_COMPARATOR); - DefaultComboBoxModel model = new DefaultComboBoxModel<>(); - combo.setModel(model); - - FontMetrics fm = combo.getFontMetrics(combo.getFont()); - int width = 0; - for (AddressSpace space : spaces) { - if (!predicate.test(space)) { - continue; - } - String s = space.toString(); - width = Math.max(width, fm.stringWidth(s)); - - model.addElement(space); - } - -// // Commented out the following 2 lines since they were causing the Combo to only -// // display "..." in some cases instead of the actual address space name. -// Dimension d = combo.getPreferredSize(); -// combo.setPreferredSize(new Dimension(width + 30, d.height)); - - if (model.getSize() > 1) { - if (!comboAdded) { - add(combo, BorderLayout.WEST); - comboAdded = true; - } - } - else if (comboAdded) { - remove(combo); - comboAdded = false; - } - invalidate(); - } - - /** - * Sets the selected combo box item - * to the default address space. - */ - public void selectDefaultAddressSpace() { - if (addrFactory != null) { - AddressSpace space = addrFactory.getDefaultAddressSpace(); - combo.setSelectedItem(space); - } + public void setAddressErrorConsumer(Consumer addressErrorConsumer) { + this.addressErrorConsumer = addressErrorConsumer; } /** @@ -273,32 +313,15 @@ public class AddressInput extends JPanel implements FocusableEditor { textField.selectAll(); } - /** - * Get the offset part of the address field. - * @return String - */ - public String getValue() { - return textField.getText(); - } - - /** - * Set the offset part of the address offset field without changing address space. - * NOTE: This method is intended for test use only and mimicks user input. - * @param value the offset value string - */ - public void setValue(String value) { - textField.setText(value); - } - /** * Set the address space and offset. * NOTE: Unlike {@link #setAddress(Address)} this method is intended for test use only - * and mimicks user input with {@link #stateChanged()} notification. + * and mimics user input with address changed notification * @param addr the address value */ - public void setValue(Address addr) { + public void simulateAddressChanged(Address addr) { setAddress(addr); - stateChanged(); + notifyAddressChanged(); } @Override @@ -318,27 +341,7 @@ public class AddressInput extends JPanel implements FocusableEditor { * @param state false means that the combo box should not be editable */ public void setAddressSpaceEditable(boolean state) { - if (!state && comboAdded) { - AddressSpace selectedSpace = (AddressSpace) combo.getSelectedItem(); - String spaceName = selectedSpace != null ? selectedSpace.getName() + ":" : " "; - spaceField = new JTextField(spaceName); - - spaceField.setEnabled(false); - remove(combo); - add(spaceField, BorderLayout.WEST); - if (textField.getText().length() == 0) { - updateSpaceField = true; - } - } - } - - /** - * Adds a change listener that will be notified anytime this address value - * in this panel changes - * @param listener the change listener to be notified. - */ - public void addChangeListener(ChangeListener listener) { - changeListener = listener; + addressSpaceField.setEditable(state); } /** @@ -352,20 +355,17 @@ public class AddressInput extends JPanel implements FocusableEditor { /** * Removes the action listener from the list to be notified. - * @param listener + * @param listener the listener to be removed */ public void removeActionListener(ActionListener listener) { this.textField.removeActionListener(listener); } - /** - * @see java.awt.Component#setEnabled(boolean) - */ @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); textField.setEnabled(enabled); - combo.setEnabled(enabled); + addressSpaceField.setEnabled(enabled); } /** @@ -377,59 +377,204 @@ public class AddressInput extends JPanel implements FocusableEditor { } /** - * Set the text field to be editable according to the state param. + * Set the text field to be editable or not. + * @param b true if the address input field can be edited */ - public void setEditable(boolean state) { - textField.setEditable(state); + public void setEditable(boolean b) { + textField.setEditable(b); + addressSpaceField.setEditable(b); } + /** + * Returns true if the address input field is editable. + * @return true if the address input field is editable. + */ public boolean isEditable() { return textField.isEditable(); } @Override public void focusEditor() { - if (comboAdded) { - combo.requestFocusInWindow(); + if (addressSpaceField.getSpaceCount() > 1 && addressSpaceField.isEnabled()) { + addressSpaceField.requestFocusInWindow(); } else { textField.requestFocusInWindow(); } } - private void stateChanged() { - if (changeListener != null && !updatingAddress && !stateChanging) { - stateChanging = true; - changeListener.stateChanged(null); - stateChanging = false; - } - } - - public void showAddressSpaceCombo(boolean showCombo) { - if (showCombo) { - if (!comboAdded) { - add(combo, BorderLayout.WEST); - comboAdded = true; - } - } - else if (comboAdded) { - remove(combo); - comboAdded = false; - } - revalidate(); - } - @Override public void requestFocus() { textField.requestFocus(); } - protected JTextField getAddressTextField() { - return textField; + private void buildComponent() { + setLayout(new BorderLayout()); + textField = new HexDecimalModeTextField(10, b -> hexModeChanged(b)); + textField.setHexMode(true); + textField.setName("JTextField");//for JUnits... + addressSpaceField = new AddressSpaceField(); + add(textField, BorderLayout.CENTER); + comboAdded = false; + + textField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + notifyAddressChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + notifyAddressChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + notifyAddressChanged(); + } + }); + } - protected JTextField getAddressSpaceTextField() { - return spaceField; + private void hexModeChanged(boolean hexMode) { + this.assumeHex = hexMode; + addressEvaluator.setAssumeHex(hexMode); + notifyAddressChanged(); } + private String removeLeadingZeros(String addressString) { + // if it has a colon, then is is a segmented address, don't mess with it. + if (addressString.indexOf(":") >= 0) { + return addressString; + } + for (int i = 0; i < addressString.length(); i++) { + if (addressString.charAt(i) != '0') { + return addressString.substring(i); + } + } + return "0"; + } + + private void updateAddressSpaceCombo() { + notificationsEnabled = false; + try { + addressSpaceField.updateAddressSpaces(addressEvaluator.getAddressFactory()); + } + finally { + notificationsEnabled = true; + } + addRemoveAdressSpaceField(); + } + + private void addRemoveAdressSpaceField() { + remove(addressSpaceField); + if (addressSpaceField.getSpaceCount() > 1) { + add(addressSpaceField, BorderLayout.WEST); + } + revalidate(); + } + + private void notifyAddressChanged() { + if (notificationsEnabled) { + try { + Address address = getAddressWithExceptions(); + addressChangedConsumer.accept(address); + } + catch (ExpressionException e) { + addressChangedConsumer.accept(null); + addressErrorConsumer.accept(e.getMessage()); + } + } + } + + private class AddressSpaceField extends JPanel { + private JComboBox 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 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 createAddressSpaceModel(AddressFactory factory) { + AddressSpace[] spaces = factory.getAddressSpaces(); + + Arrays.sort(spaces, ADDRESS_SPACE_SORT_COMPARATOR); + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + + for (AddressSpace space : spaces) { + if (!addressSpaceFilter.test(space)) { + continue; + } + model.addElement(space); + } + return model; + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressSetEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressSetEditorPanel.java index d1762da48d..c2d93997b1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressSetEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressSetEditorPanel.java @@ -4,9 +4,9 @@ * 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. @@ -67,10 +67,7 @@ public class AddressSetEditorPanel extends JPanel { JLabel minLabel = new GDLabel("Min:"); minLabel.setToolTipText("Enter minimum address to add or remove"); minAddressPanel.add(minLabel, BorderLayout.WEST); - minAddressField = new AddressInput(); - minAddressField.setAddressFactory(addressFactory); - ChangeListener listener = e -> validateAddRemoveButton(); - minAddressField.addChangeListener(listener); + minAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton()); minAddressPanel.add(minAddressField, BorderLayout.CENTER); JPanel maxAddressPanel = new JPanel(); @@ -78,9 +75,7 @@ public class AddressSetEditorPanel extends JPanel { JLabel maxLabel = new GDLabel("Max:"); maxLabel.setToolTipText("Enter maximum address to add or remove"); maxAddressPanel.add(maxLabel, BorderLayout.WEST); - maxAddressField = new AddressInput(); - maxAddressField.setAddressFactory(addressFactory); - maxAddressField.addChangeListener(listener); + maxAddressField = new AddressInput(addressFactory, a -> validateAddRemoveButton()); maxAddressPanel.add(maxAddressField, BorderLayout.CENTER); maxAddressPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); addRangeButton = new GButton(ADD_ICON); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java index 4e7fc8cb7a..d9e3038a53 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java @@ -338,7 +338,7 @@ public class OptionsEditorPanel extends JPanel { return null; } AddressFactory addressFactory = addressFactoryService.getAddressFactory(); - AddressInput addressInput = new AddressInput(); + AddressInput addressInput = new AddressInput(a -> option.setValue(a)); addressInput.setName(option.getName()); Address addr = (Address) option.getValue(); if (addr == null && addressFactory != null) { @@ -347,7 +347,6 @@ public class OptionsEditorPanel extends JPanel { } addressInput.setAddressFactory(addressFactory); addressInput.setAddress(addr); - addressInput.addChangeListener(e -> option.setValue(addressInput.getAddress()));// addressInput.addActionListener(e -> option.setValue(addressInput.getAddress())); return addressInput; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java index 3ddf1be9c7..26651775cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/CPP/DefineTable.java @@ -5,9 +5,9 @@ * 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. @@ -18,9 +18,9 @@ package ghidra.app.util.cparser.CPP; import java.util.*; +import generic.expressions.ExpressionEvaluator; import ghidra.app.util.cparser.CPP.PreProcessor.PPToken; import ghidra.program.model.data.*; -import ghidra.program.util.AddressEvaluator; import ghidra.util.Msg; /** @@ -281,14 +281,13 @@ public class DefineTable { StringBuffer buf = new StringBuffer(image); int lastReplPos = pos; - + boolean initialListSupplied = initialList != null; // initial list passed in ArrayList sublist = new ArrayList(); if (initialList != null) { sublist.addAll(initialList); } - // don't replace an infinite number of times. Fail safe for possible ininite loop while (pos < buf.length() && replaceCount < ARBITRARY_MAX_REPLACEMENTS) { // clear list of used macros when move past replacement area @@ -353,7 +352,8 @@ public class DefineTable { return true; } - int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList sublist, boolean initialList) { + int replace(StringBuffer buf, String currKey, int fromIndex, ArrayList sublist, + boolean initialList) { String replacementString = null; if (sublist == null) { @@ -451,11 +451,11 @@ public class DefineTable { argValue + " args processed : " + argsfound); return replString; } - + // Handle "..." varargs // if last argument is ellipsis, then is varargs, replace the rest of the params String curArgName = argv.elementAt(index).image; - if (index == argv.size()-1 && VARARG_ELLIPSIS.equals(curArgName)) { + if (index == argv.size() - 1 && VARARG_ELLIPSIS.equals(curArgName)) { isVarArg = true; // Replace __VA_ARGS__ with the rest of params curArgName = "__VA_ARGS__"; @@ -526,14 +526,14 @@ public class DefineTable { startpos = end; } buf.append(substString.substring(startpos)); - + // Handle __VA_OPT__() // if varargs and no more params, replace with "" // if varargs and has vararg params, replace with if (isVarArg) { replace_VaOpt(buf, hadVarArgs); } - + substString = buf.toString(); return substString; } @@ -544,28 +544,29 @@ public class DefineTable { * @param buf string buffer to replace __VA_OPT__(value) within * @param hadVarArgs */ - private void replace_VaOpt(StringBuffer buf, boolean hadVarArgs) { + private void replace_VaOpt(StringBuffer buf, boolean hadVarArgs) { int optIdx = buf.indexOf("__VA_OPT__"); if (optIdx < 0) { return; } - - int lparen = buf.indexOf("(", optIdx+1); + + int lparen = buf.indexOf("(", optIdx + 1); if (lparen < 0) { return; } - - int rparen = buf.indexOf(")",lparen+1); + + int rparen = buf.indexOf(")", lparen + 1); if (rparen < 0) { return; } - + // get in between string. - String replarg = buf.substring(lparen+1, rparen); + String replarg = buf.substring(lparen + 1, rparen); if (hadVarArgs) { - buf.replace(optIdx, rparen+1, replarg); - } else { - buf.replace(optIdx, rparen+1, ""); + buf.replace(optIdx, rparen + 1, replarg); + } + else { + buf.replace(optIdx, rparen + 1, ""); } } @@ -629,7 +630,7 @@ public class DefineTable { public String expand(String image, boolean join) { return expand(image, join, null); } - + /** * do the final expansion of "##" concats in the define strings that protect normal macro substitution. * @@ -639,7 +640,7 @@ public class DefineTable { * @return */ public String expand(String image, boolean join, ArrayList list) { - + image = macroSub(image, 0, list); // get rid of ## constructs @@ -705,13 +706,13 @@ public class DefineTable { Iterator iter = getDefineNames(); while (iter.hasNext()) { String defName = iter.next(); - + String strValue = expandDefine(defName); if (strValue == null) { // couldn't expand, must have been a macro continue; } - + // strip off any casting/parentheses strValue = stripCast(strValue); @@ -720,7 +721,7 @@ public class DefineTable { if (lvalue == null) { try { - lvalue = AddressEvaluator.evaluateToLong(strValue); + lvalue = ExpressionEvaluator.evaluateToLong(strValue); } catch (Exception exc) { // ignore didn't parse well @@ -738,7 +739,8 @@ public class DefineTable { dtMgr.endTransaction(transactionID, true); } - public void populateDefineEquate(DataTypeManager openDTMgrs[], DataTypeManager dtMgr, String category, String prefix, String defName, long value) { + public void populateDefineEquate(DataTypeManager openDTMgrs[], DataTypeManager dtMgr, + String category, String prefix, String defName, long value) { String enumName = prefix + defName; // Start the Enum at 8, then resize to fit the value @@ -751,33 +753,33 @@ public class DefineTable { CategoryPath path = getCategory(currentCategoryName); path = new CategoryPath(path, category); enuum.setCategoryPath(path); - + DataType dt = resolveDataType(openDTMgrs, path, enuum); dtMgr.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER); } - - private DataType resolveDataType(DataTypeManager openDTMgrs[], CategoryPath path, DataType dt) { - if (openDTMgrs == null) { - return dt; - } - // If the exact data type exists in any open DTMgr, use the open DTmgr type - // instead - for (int i = 0; i < openDTMgrs.length; i++) { - // look for the data type by name - // equivalent, return it - // look for the data type by category - // equivalent, return it - DataType candidateDT = openDTMgrs[i].getDataType(dt.getCategoryPath(), dt.getName()); - - if (candidateDT != null && candidateDT.isEquivalent(candidateDT)) { - return candidateDT; - } - } + private DataType resolveDataType(DataTypeManager openDTMgrs[], CategoryPath path, DataType dt) { + if (openDTMgrs == null) { + return dt; + } + // If the exact data type exists in any open DTMgr, use the open DTmgr type + // instead - return dt; - } + for (int i = 0; i < openDTMgrs.length; i++) { + // look for the data type by name + // equivalent, return it + // look for the data type by category + // equivalent, return it + DataType candidateDT = openDTMgrs[i].getDataType(dt.getCategoryPath(), dt.getName()); + + if (candidateDT != null && candidateDT.isEquivalent(candidateDT)) { + return candidateDT; + } + } + + return dt; + } public String expandDefine(String defName) { // don't worry about macros @@ -789,13 +791,13 @@ public class DefineTable { // check if this is a numeric expression that could be simplified // String strValue = getValue(defName); - + ArrayList list = new ArrayList(); list.add(defName); - + String strExpanded = expand(strValue, true, list); strValue = strExpanded; - + return strValue; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/dialog/AskAddrDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/dialog/AskAddrDialog.java index c6361928f8..24c4ef54fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/dialog/AskAddrDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/dialog/AskAddrDialog.java @@ -4,9 +4,9 @@ * 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. @@ -25,19 +25,17 @@ import docking.DockingWindowManager; import docking.widgets.label.GLabel; import ghidra.app.util.AddressInput; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.listing.Program; public class AskAddrDialog extends DialogComponentProvider { private boolean isCanceled; private AddressInput addrInput; - public AskAddrDialog(final String title, final String message, AddressFactory af, + public AskAddrDialog(final String title, final String message, Program program, Address lastAddr) { super(title, true, true, true, false); - addrInput = new AddressInput(); - addrInput.setAddressFactory(af); - addrInput.selectDefaultAddressSpace(); + addrInput = new AddressInput(program); if (lastAddr != null) { addrInput.setAddress(lastAddr); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java index 88bf1372ad..1ef9c9b33d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java @@ -4,9 +4,9 @@ * 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. @@ -251,7 +251,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest JButton okButton = findButton(d.getComponent(), "OK"); runSwing(() -> { - addrField.setValue("0x200"); + addrField.setText("0x200"); nameField.setText(".test"); lengthField.setText("0x100"); commentField.setText("this is a block test"); @@ -319,7 +319,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest JButton okButton = findButton(d.getComponent(), "OK"); runSwing(() -> { - addrField.setValue("0x01001200"); + addrField.setText("0x01001200"); nameField.setText(".test"); lengthField.setText("0x100"); commentField.setText("this is a block test"); @@ -354,7 +354,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest JButton okButton = findButton(d.getComponent(), "OK"); runSwing(() -> { - addrField.setValue("xxxxx"); + addrField.setText("xxxxx"); nameField.setText(".test"); lengthField.setText("0x100"); commentField.setText("this is a block test"); @@ -364,7 +364,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); - assertEquals("Please enter a valid Start Address", msg); + assertEquals("Invalid Address: Could not evaluate token \"xxxxx\"", msg); assertFalse(okButton.isEnabled()); close(d); } @@ -768,7 +768,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); assertEquals("Please enter a source address for the bit block", msg); - runSwing(() -> addrField.setValue("01001000")); + runSwing(() -> addrField.setText("01001000")); } else { assertTrue(okButton.isEnabled()); @@ -844,7 +844,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); assertEquals("Please enter a source address for the bit block", msg); - runSwing(() -> addrField.setValue("01001000")); + runSwing(() -> addrField.setText("01001000")); } else { assertTrue(okButton.isEnabled()); @@ -1040,7 +1040,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest // move the block to 0x300 Address newStart = startAddr.getAddressSpace().getAddressInThisSpaceOnly(0x300); Address newEnd = startAddr.getAddressSpace().getAddressInThisSpaceOnly(0x3ff); - runSwing(() -> startField.setValue(newStart)); + runSwing(() -> startField.simulateAddressChanged(newStart)); assertEquals(newEnd, endField.getAddress()); assertTrue(okButton.isEnabled()); @@ -1110,7 +1110,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); // enter an invalid address - runSwing(() -> startField.setValue(getAddr(0x0300).toString() + "gggg")); + runSwing(() -> startField.setText(getAddr(0x0300).toString() + "gggg")); assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); assertEquals("Invalid Address", msg); @@ -1168,7 +1168,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); // enter an invalid address - runSwing(() -> endField.setValue(getAddr(0x0300).toString() + "gggg")); + runSwing(() -> endField.setText(getAddr(0x0300).toString() + "gggg")); assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); assertEquals("Invalid Address", msg); @@ -1227,8 +1227,8 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest // enter an invalid address runSwing(() -> { - startField.setValue(getAddr(0x1000).toString()); - endField.setValue(getAddr(0x10).toString()); + startField.setText(getAddr(0x1000).toString()); + endField.setText(getAddr(0x10).toString()); }); assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); @@ -1287,7 +1287,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest assertFalse(okButton.isEnabled()); // enter an invalid address - runSwing(() -> startField.setValue("00000000")); + runSwing(() -> startField.setText("00000000")); assertFalse(okButton.isEnabled()); String msg = findLabelStr(d.getComponent(), "statusLabel"); assertEquals("Block is already at 00000000", msg); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java index 1b6c087164..cae5725385 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java @@ -4,9 +4,9 @@ * 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. @@ -231,7 +231,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest (RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> blockOneEnd.setValue("01003000")); + runSwing(() -> blockOneEnd.setText("01003000")); assertEquals(0x2001, blockOneLength.getValue().longValue()); assertEquals(getAddr(0x01003001), blockTwoStart.getAddress()); assertEquals("010075ff", blockTwoEnd.getText()); @@ -277,7 +277,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest (RegisterField) findComponentByName(d.getComponent(), "BlockTwoLength"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> blockTwoStart.setValue("01003000")); + runSwing(() -> blockTwoStart.setText("01003000")); assertEquals(0x2000, blockOneLength.getValue().longValue()); assertEquals(getAddr(0x01002fff), blockOneEnd.getAddress()); assertEquals("010075ff", blockTwoEnd.getText()); @@ -357,7 +357,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest (AddressInput) findComponentByName(d.getComponent(), "BlockOneEnd"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> blockOneEnd.setValue("01000")); + runSwing(() -> blockOneEnd.setText("01000")); assertFalse(okButton.isEnabled()); assertEquals("End address must be greater than start", findLabelStr(d.getComponent(), "statusLabel")); @@ -378,7 +378,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest (AddressInput) findComponentByName(d.getComponent(), "BlockTwoStart"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> blockTwoStart.setValue("01000")); + runSwing(() -> blockTwoStart.setText("01000")); assertFalse(okButton.isEnabled()); assertEquals("Start address must be greater than original block start (01001000)", findLabelStr(d.getComponent(), "statusLabel")); @@ -531,7 +531,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> start.setValue("00002000")); + runSwing(() -> start.setText("00002000")); assertEquals("0x1005600", length.getText()); assertTrue(okButton.isEnabled()); @@ -579,7 +579,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest assertNotNull(length); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> start.setValue("01201000")); + runSwing(() -> start.setText("01201000")); assertFalse(okButton.isEnabled()); assertEquals("Start must be less than 01001000", findLabelStr(d.getComponent(), "statusLabel")); @@ -681,7 +681,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest JTextField end = (JTextField) findComponentByName(d.getComponent(), "EndAddress"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> start.setValue("01008000")); + runSwing(() -> start.setText("01008000")); assertEquals("0100f3ff", end.getText()); assertTrue(okButton.isEnabled()); @@ -740,7 +740,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest RegisterField length = (RegisterField) findComponentByName(d.getComponent(), "BlockLength"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> end.setValue("01007700")); + runSwing(() -> end.setText("01007700")); assertEquals("0x6701", length.getText()); assertTrue(okButton.isEnabled()); @@ -798,7 +798,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest AddressInput end = (AddressInput) findComponentByName(d.getComponent(), "EndAddress"); JButton okButton = findButton(d.getComponent(), "OK"); - runSwing(() -> end.setValue("01007000")); + runSwing(() -> end.setText("01007000")); assertFalse(okButton.isEnabled()); assertEquals("End must be greater than 010075ff", findLabelStr(d.getComponent(), "statusLabel")); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/AddressInputTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/AddressInputTest.java new file mode 100644 index 0000000000..b8bc8442c0 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/AddressInputTest.java @@ -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 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; + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AddressValueTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AddressValueTest.java index 7ad096c428..21fc1c5ba7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AddressValueTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AddressValueTest.java @@ -4,9 +4,9 @@ * 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. @@ -21,7 +21,6 @@ import org.junit.Test; import docking.widgets.values.AbstractValue; import ghidra.app.util.AddressInput; -import ghidra.features.base.values.AddressValue; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; @@ -144,7 +143,7 @@ public class AddressValueTest extends AbstractValueIntegrationTest { protected void setTextOnAddressInput(AbstractValue nameValue, String text) { runSwing(() -> { AddressInput addressInput = (AddressInput) nameValue.getComponent(); - addressInput.setValue(text); + addressInput.setText(text); }); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ValuesMapDialogParseErrorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ValuesMapDialogParseErrorTest.java index 34325b409b..dfa1fcec4a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ValuesMapDialogParseErrorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ValuesMapDialogParseErrorTest.java @@ -4,9 +4,9 @@ * 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. @@ -43,7 +43,7 @@ public class ValuesMapDialogParseErrorTest extends AbstractValueIntegrationTest protected void setTextOnAddressInput(AbstractValue nameValue, String text) { runSwing(() -> { AddressInput addressInput = (AddressInput) nameValue.getComponent(); - addressInput.setValue(text); + addressInput.setText(text); }); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/AddressEvaluatorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/AddressEvaluatorTest.java index a91bfd4cb6..2f4eba2a59 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/AddressEvaluatorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/AddressEvaluatorTest.java @@ -4,9 +4,9 @@ * 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. @@ -17,103 +17,146 @@ package ghidra.program.util; import static org.junit.Assert.*; -import org.junit.Test; +import org.junit.*; +import generic.test.AbstractGenericTest; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.Symbol; -import ghidra.test.AbstractGhidraHeadedIntegrationTest; -/** - * - * - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class AddressEvaluatorTest extends AbstractGhidraHeadedIntegrationTest { +public class AddressEvaluatorTest extends AbstractGenericTest { - AddressFactory addrFactory; + private Program program; + private ProgramBuilder builder; + private Symbol entry; + private Symbol entryInFoo; public AddressEvaluatorTest() { super(); } - private Address addr(String address) { - return addrFactory.getAddress(address); + @Before + public void setUp() throws Exception { + builder = new ProgramBuilder("Test", ProgramBuilder._TOY_LE, this); + builder.createMemory("TestBlock", "0x100", 100); + program = builder.getProgram(); + entry = builder.createLabel("0x100", "entry"); + builder.createNamespace("foo"); + entryInFoo = builder.createLabel("0x103", "entry", "foo"); + } + + @After + public void tearDown() { + program.release(this); } @Test - public void testEval() throws Exception { - Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this); - addrFactory = p.getAddressFactory(); - int txId = p.startTransaction("Test"); - try { - assertEquals(addr("0x19"), AddressEvaluator.evaluate(p, "(2+3)*5")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+3*5")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+(3*5)")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "(2+3*5)")); - assertEquals(addr("0x16"), AddressEvaluator.evaluate(p, "0x11+5")); - assertEquals(addr("0x02"), AddressEvaluator.evaluate(p, "2-1+1")); - assertEquals(addr("0x5"), AddressEvaluator.evaluate(p, "5")); - assertEquals(addr("0x3"), AddressEvaluator.evaluate(p, "0-5+8")); - assertEquals(addr("0x3"), AddressEvaluator.evaluate(p, "-5+8")); - assertEquals(addr("0xfffffffB"), AddressEvaluator.evaluate(p, "-5")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "3+(5+(3*2)+(3))")); - assertEquals(addr("0xff00"), AddressEvaluator.evaluate(p, "0xffff ^ 0xff")); - assertEquals(addr("0x123f"), AddressEvaluator.evaluate(p, "0xffff & 0x123f")); - assertEquals(addr("0x1234"), AddressEvaluator.evaluate(p, "0x1200 | 0x0034")); - assertEquals(addr("0xffffffff"), AddressEvaluator.evaluate(p, "~ 0x0")); - assertEquals(addr("0x1201"), AddressEvaluator.evaluate(p, "0x1200 | ~(0xfffffffe)")); - assertEquals(addr("0x480"), AddressEvaluator.evaluate(p, "0x1200 >> 2")); - assertEquals(addr("0x1200"), AddressEvaluator.evaluate(p, "0x480 << 2")); + public void testLongValueExpression() { + assertEval(addr("0x19"), "(2+3)*5"); + assertEval(addr("0x11"), "2+3*5"); + assertEval(addr("0x11"), "2+(3*5)"); + assertEval(addr("0x3"), "0-5+8"); + assertEval(addr("0x3"), "-5+8"); + assertEval(addr("0xfffffffB"), "-5"); + assertEval(addr("0x11"), "3+(5+(3*2)+(3))"); + } - assertEquals(addr("0x1"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x2)")); - assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x1)")); - assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) == 0x1)")); + @Test + public void testAssumesHex() { + assertEval(addr("0x30"), "20 + 10"); + assertEval(addr("0xf1"), "e1+10"); + } - assertEquals(addr("0x1"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) >= 0x1)")); - assertEquals(addr("0x0"), AddressEvaluator.evaluate(p, "(((0x1 | 0x2) & 0x2) <= 0x1)")); - - 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)")); + @Test + public void testAcceptsHexPrefix() { + assertEval(addr("0x16"), "0x11+5"); + assertEval(addr("0x35"), "20+0x15"); + } - Symbol s = p.getSymbolTable().createLabel(addr("0x100"), "entry", SourceType.IMPORTED); - Address a = s.getAddress(); - a = a.add(10); - assertEquals(a, AddressEvaluator.evaluate(p, "entry+5*2")); - assertEquals(addr("0x101"), AddressEvaluator.evaluate(p, "entry + (entry == 0x100)")); - assertEquals(addr("0x500"), AddressEvaluator.evaluate(p, - "entry + (entry == 0x100) * 0x400 + (entry < 0x100) * 0x500")); - assertEquals(addr("0x600"), AddressEvaluator.evaluate(p, - "entry + (entry > 0x100) * 0x400 + (entry <= 0x100) * 0x500")); - } - finally { - p.endTransaction(txId, true); - p.release(this); - } + @Test + public void 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 public void testMultiAddrSpace() throws Exception { - Program p = createDefaultProgram("Test", ProgramBuilder._TOY_LE, this); - addrFactory = p.getAddressFactory(); - try { - assertEquals(addr("0x19"), AddressEvaluator.evaluate(p, "(2+3)*5")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+3*5")); - assertEquals(addr("0x11"), AddressEvaluator.evaluate(p, "2+(3*5)")); - assertEquals(addr("RAM:15"), AddressEvaluator.evaluate(p, "RAM:2 + 0x13")); - } - finally { - p.release(this); - } + assertEval(addr("0x15"), "ram:2 + 0x13"); + assertEval(addr("register:0x15"), "register:2 + 0x13"); + } + + private void assertEval(Address addr, String input) { + assertEquals(addr, AddressEvaluator.evaluate(program, input)); + } + + private Address addr(String address) { + return program.getAddressFactory().getAddress(address); } } diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerOptionsDialog.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerOptionsDialog.java index 71fd8a2006..5acec1ab73 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerOptionsDialog.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerOptionsDialog.java @@ -4,9 +4,9 @@ * 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. @@ -81,12 +81,11 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider if (provider instanceof ProgramByteViewerComponentProvider) { Program program = ((ProgramByteViewerComponentProvider) provider).getProgram(); if (program != null) { - addressInputField = new AddressInput(); - addressInputField.setAddressFactory(program.getAddressFactory()); - addressInputField.showAddressSpaceCombo(false); - addressInputField.setAddress(getAlignmentAddress()); + Address alignment = getAlignmentAddress(); + addressInputField = new AddressInput(program, a -> update()); + addressInputField.setAddressSpaceFilter(s -> s == alignment.getAddressSpace()); + addressInputField.setAddress(alignment); panel.add(addressInputField); - addressInputField.addChangeListener(this); addressInputField.setAccessibleName("Alignment Address"); } } @@ -214,13 +213,13 @@ public class ByteViewerOptionsDialog extends DialogComponentProvider } private boolean hasValidFieldValues() { - if (addressInputField.getValue().length() == 0) { + if (addressInputField.getText().length() == 0) { setStatusText("Enter an alignment address"); return false; } Address alignmentAddress = addressInputField.getAddress(); if (alignmentAddress == null) { - setStatusText("Invalid alignment address:" + addressInputField.getValue()); + setStatusText("Invalid alignment address:" + addressInputField.getText()); return false; } BigInteger bytesPerLine = bytesPerLineField.getValue(); diff --git a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java index e5abd211ce..6361c1058d 100644 --- a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java +++ b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java @@ -4,9 +4,9 @@ * 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. @@ -553,7 +553,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { // current offset is 0 assertEquals(program.getMinAddress(), ai.getAddress()); - runSwing(() -> ai.setValue("0100100b")); + runSwing(() -> ai.setText("0100100b")); pressButtonByText(d.getComponent(), "OK"); // verify that offset label on the plugin shows '5' assertEquals(5, plugin.getProvider().getOffset()); @@ -587,7 +587,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { // current offset is 0 assertEquals(program.getMinAddress(), ai.getAddress()); - runSwing(() -> ai.setValue("0000:0c06")); + runSwing(() -> ai.setText("0000:0c06")); pressButtonByText(d.getComponent(), "OK"); assertEquals(10, plugin.getProvider().getOffset()); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/EditableListingAddress.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/EditableListingAddress.java index d78b459941..f3c223b31e 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/EditableListingAddress.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/EditableListingAddress.java @@ -4,9 +4,9 @@ * 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. @@ -55,8 +55,7 @@ public class EditableListingAddress extends DisplayableListingAddress implements private void buildPanel() { setLayout(new PairLayout(5, 5, 50)); - addressField = new AddressInput(); - addressField.setAddressFactory(program.getAddressFactory()); + addressField = new AddressInput(program); if (address != null) { addressField.setAddress(address); } @@ -78,7 +77,7 @@ public class EditableListingAddress extends DisplayableListingAddress implements Address selectedAddress = addressField.getAddress(); if (selectedAddress == null) { throw new InvalidInputException( - "\"" + addressField.getValue() + "\" is not a valid address."); + "\"" + addressField.getText() + "\" is not a valid address."); } if (!program.getMemory().contains(selectedAddress)) { throw new InvalidInputException( diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/AddRemoveAddressRangeDialog.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/AddRemoveAddressRangeDialog.java index ba05155d14..850b07c517 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/AddRemoveAddressRangeDialog.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/AddRemoveAddressRangeDialog.java @@ -4,9 +4,9 @@ * 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. @@ -24,7 +24,6 @@ import docking.widgets.label.GDLabel; import ghidra.app.util.AddressInput; import ghidra.app.util.HelpTopics; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.layout.PairLayout; @@ -32,7 +31,6 @@ import ghidra.util.layout.PairLayout; public class AddRemoveAddressRangeDialog extends DialogComponentProvider { private Program program; - private AddressFactory addressFactory; private AddressRangeListener listener; private JPanel addressRangePanel; @@ -45,7 +43,6 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider { AddressRangeListener listener) { super(programIndicator + " Address Range", true, true, true, false); this.program = program; - addressFactory = program.getAddressFactory(); this.listener = listener; setHelpLocation(new HelpLocation(HelpTopics.LABEL, "AddEditDialog")); addWorkPanel(createAddressRangePanel()); @@ -68,8 +65,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider { minLabel.setToolTipText("Enter minimum address to add or remove"); addressRangePanel.add(minLabel); - minAddressField = new AddressInput(); - minAddressField.setAddressFactory(addressFactory); + minAddressField = new AddressInput(program); Dimension minPreferredSize = getPreferredSize(); minPreferredSize.width = 200; minAddressField.setPreferredSize(minPreferredSize); @@ -79,8 +75,7 @@ public class AddRemoveAddressRangeDialog extends DialogComponentProvider { maxLabel.setToolTipText("Enter maximum address to add or remove"); addressRangePanel.add(maxLabel); - maxAddressField = new AddressInput(); - maxAddressField.setAddressFactory(addressFactory); + maxAddressField = new AddressInput(program); Dimension maxPreferredSize = getPreferredSize(); maxPreferredSize.width = 200; minAddressField.setPreferredSize(maxPreferredSize); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java new file mode 100644 index 0000000000..d77ddc9894 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java @@ -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 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); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java index b2236767fa..25deb873be 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java @@ -4,9 +4,9 @@ * 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. @@ -15,20 +15,16 @@ */ package docking.widgets.textfield; -import java.awt.*; -import java.awt.event.*; +import java.awt.event.ActionListener; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import javax.swing.*; +import javax.swing.JComponent; +import javax.swing.JTextField; import javax.swing.event.*; import javax.swing.text.*; -import docking.DockingUtils; -import docking.util.GraphicsUtils; -import generic.theme.GThemeDefaults.Colors.Messages; -import generic.theme.Gui; import ghidra.util.SystemUtilities; /** @@ -63,12 +59,11 @@ import ghidra.util.SystemUtilities; */ public class IntegerTextField { - private JTextField textField; + private HexDecimalModeTextField textField; private boolean isHexMode = false; private boolean allowsNegative = true; private boolean allowsHexPrefix = true; - private boolean showNumbericDecoration = true; private BigInteger maxValue; private BigInteger minValue; @@ -109,9 +104,13 @@ public class IntegerTextField { * @param initialValue the initial value */ public IntegerTextField(int columns, BigInteger initialValue) { - textField = new MyTextField(columns); + textField = new HexDecimalModeTextField(columns, b -> textFieldHexModeChanged(b)); + + AbstractDocument document = (AbstractDocument) textField.getDocument(); + document.setDocumentFilter(new HexDecimalDocumentFilter()); setValue(initialValue); - textField.getDocument().addDocumentListener(new DocumentListener() { + + document.addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { @@ -266,8 +265,7 @@ public class IntegerTextField { * @param show true to show the radix mode. */ public void setShowNumberMode(boolean show) { - this.showNumbericDecoration = show; - textField.repaint(); + textField.setShowNumberMode(show); } /** @@ -278,9 +276,15 @@ public class IntegerTextField { * the current value from decimal to hex. */ public void setHexMode() { - BigInteger currentValue = getValue(); - isHexMode = true; - updateTextField(currentValue); + BigInteger value = getValue(); + setHexMode(true); + setValue(value); + + } + + private void setHexMode(boolean hexMode) { + this.isHexMode = hexMode; + textField.setHexMode(hexMode); } /** @@ -291,9 +295,9 @@ public class IntegerTextField { * current value from hex to decimal. */ public void setDecimalMode() { - BigInteger currentValue = getValue(); - isHexMode = false; - updateTextField(currentValue); + BigInteger value = getValue(); + setHexMode(false); + setValue(value); } /** @@ -477,6 +481,12 @@ public class IntegerTextField { textField.setHorizontalAlignment(alignment); } + private void textFieldHexModeChanged(boolean hexMode) { + BigInteger value = getValue(); + this.isHexMode = hexMode; + setValue(value); + } + private String computeTextForValue(BigInteger value) { if (value == null) { return ""; @@ -525,15 +535,6 @@ public class IntegerTextField { } } - private void toggleMode() { - if (isHexMode) { - setDecimalMode(); - } - else { - setHexMode(); - } - } - private boolean passesMaxCheck(BigInteger value) { if (value == null) { return true; @@ -648,7 +649,7 @@ public class IntegerTextField { if (isValidPrefix(valueString)) { // When the input is valid, update the hex mode to match how the text was parsed. // See parseAsHex variable comment above. - isHexMode = parseAsHex; + setHexMode(parseAsHex); return true; } @@ -661,7 +662,7 @@ public class IntegerTextField { if (passesMaxCheck(value) && passesMinCheck(value)) { // When the input is valid, update the hex mode to match how the text was parsed. // See parseAsHex variable comment above. - isHexMode = parseAsHex; + setHexMode(parseAsHex); return true; } } @@ -692,76 +693,4 @@ public class IntegerTextField { // so we don't allow negatives return value.signum() < 0; } - - /** - * Overrides the JTextField mainly to allow hint painting for the current radix mode. - */ - private class MyTextField extends JTextField { - - private static final String FONT_ID = "font.input.hint"; - private int hintWidth; - - public MyTextField(int columns) { - super(columns); - - FontMetrics fontMetrics = getFontMetrics(Gui.getFont(FONT_ID)); - String mode = isHexMode ? "Hex" : "Dec"; - hintWidth = fontMetrics.stringWidth(mode); - - AbstractDocument document = (AbstractDocument) getDocument(); - document.setDocumentFilter(new HexDecimalDocumentFilter()); - - addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_M && DockingUtils.isControlModifier(e)) { - toggleMode(); - repaint(); - } - } - }); - - // make sure tooltips will be activated - ToolTipManager.sharedInstance().registerComponent(this); - } - - @Override - public String getToolTipText(MouseEvent event) { - - int hintStart = getBounds().width - hintWidth; - if (event.getX() > hintStart) { - String key = DockingUtils.CONTROL_KEY_NAME; - return "Press '" + key + "-M' to toggle Hex or Decimal Mode"; - } - - return null; - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - if (!showNumbericDecoration) { - return; - } - - Font savedFont = g.getFont(); - g.setFont(Gui.getFont(FONT_ID)); - g.setColor(Messages.HINT); - - Dimension size = getSize(); - Insets insets = getInsets(); - int x; - if (getHorizontalAlignment() == RIGHT) { - x = insets.left; - } - else { - x = size.width - insets.right - hintWidth; - } - int y = size.height - insets.bottom - 1; - String mode = isHexMode ? "Hex" : "Dec"; - GraphicsUtils.drawString(this, g, mode, x, y); - g.setFont(savedFont); - } - } - } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionElement.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionElement.java new file mode 100644 index 0000000000..912cc39231 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionElement.java @@ -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 +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionEvaluator.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionEvaluator.java new file mode 100644 index 0000000000..34f538f71c --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionEvaluator.java @@ -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. + *

    + * 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". + *

    + * 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 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 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 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 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 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 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 list) throws ExpressionException { + List> 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 set : ops) { + processBinaryOperators(list, set); + } + } + + private void processBinaryOperators(List list, + Set 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 list, + Set 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 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 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 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 list) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) == LEFT_PAREN) { + return i; + } + } + return -1; + } + + private int findGroupEnd(List 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 list) { + ExpressionElement lastElement = list.get(list.size() - 1); + return (ExpressionOperator) lastElement; + } + + private boolean processSymbol(List 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 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 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 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 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 list, String token) { + try { + long value = NumericUtilities.parseHexLong(token); + list.add(new LongExpressionValue(value)); + return true; + } + catch (NumberFormatException e) { + // ignore + } + return false; + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionException.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionException.java new file mode 100644 index 0000000000..8abc8eeaf5 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionException.java @@ -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); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionGrouper.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionGrouper.java new file mode 100644 index 0000000000..5310487fed --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionGrouper.java @@ -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; +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionOperator.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionOperator.java new file mode 100644 index 0000000000..c9180c96b8 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionOperator.java @@ -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> 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> getBinaryOperatorsByPrecedence() { + if (binaryOperatorsByPrecedence == null) { + binaryOperatorsByPrecedence = buildOperatorsByPrecedenceList(); + } + return binaryOperatorsByPrecedence; + } + + private static List> buildOperatorsByPrecedenceList() { + ExpressionOperator[] values = values(); + LazyMap> 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 + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionValue.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionValue.java new file mode 100644 index 0000000000..3cf72bab57 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/ExpressionValue.java @@ -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; + +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/expressions/LongExpressionValue.java b/Ghidra/Framework/Generic/src/main/java/generic/expressions/LongExpressionValue.java new file mode 100644 index 0000000000..200a30e21b --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/expressions/LongExpressionValue.java @@ -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!"); + } + + } + +} diff --git a/Ghidra/Framework/Generic/src/test/java/generic/expressions/ExpressionEvaluatorTest.java b/Ghidra/Framework/Generic/src/test/java/generic/expressions/ExpressionEvaluatorTest.java new file mode 100644 index 0000000000..c7d974b044 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/generic/expressions/ExpressionEvaluatorTest.java @@ -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)); + + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressEvaluator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressEvaluator.java index 665591baaf..ae03a5a009 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressEvaluator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressEvaluator.java @@ -4,9 +4,9 @@ * 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. @@ -15,656 +15,270 @@ */ package ghidra.program.util; -import java.util.*; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.List; +import generic.expressions.*; +import ghidra.app.util.NamespaceUtils; +import ghidra.app.util.SymbolPath; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.Symbol; -import ghidra.program.model.symbol.SymbolTable; -import ghidra.util.NumericUtilities; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.*; /** - * The AddressEvaluator class provides a way to evaluate a string - * that represents an address and resolve it to an address for a particular program. + * Class for evaluating expressions as an Address. See + * {@link ExpressionOperator} for the full list of supported operators. All values are interpreted + * as longs or symbols that resolve to an address. + *

    + * 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". + *

    + * 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 programReference; + private AddressFactory addressFactory; + private AddressSpace preferredSpace; /** - * Gets a legitimate address for the specified program as indicated by the string. + * Gets a valid address for the specified program as indicated by the input expression. * @param p the program to use for determining the address. - * @param baseAddr the base address to use for relative addressing. - * @param s string representation of the address desired. + * @param inputExpression string representation of the address desired. * @return the address. Otherwise, return null if the string fails to evaluate * to a unique legitimate address. */ - public static Address evaluate(Program p, Address baseAddr, String s) { - - AddressFactory af = p.getAddressFactory(); - SymbolTable st = p.getSymbolTable(); - List list = new ArrayList(); - 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 list = new ArrayList(); - - 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 list) { - StringTokenizer parser = new StringTokenizer(s, TOKEN_CHARS, true); - String lookahead = null; - while (lookahead != null || parser.hasMoreTokens()) { - String tok = null; - if (lookahead != null) { - tok = lookahead; - lookahead = null; - } - else { - tok = parser.nextToken(); - } - - if (tok.equals(" ")) { - continue; - } - - // = must be followed by =, others can be followed - if ("=!<>|&".contains(tok)) { - lookahead = parser.nextToken(); - tok = checkDoubleToken(tok, lookahead); - // if tok is now longer, consumed lookahead - if (tok.length() > 1) { - lookahead = null; - } - } - Object obj = Operator.getOperator(tok); - if (obj == null) { - obj = getValueObject(st, af, tok); - } - if (obj == null) { - return false; - } - list.add(obj); - } - return true; - } - - private static String checkDoubleToken(String tok, String lookahead) { - switch (tok) { - case "=": - if (lookahead.equals("=")) { - return "=="; - } - break; - - case "<": - if (lookahead.equals("=")) { - return "<="; - } - if (lookahead.equals("<")) { - return "<<"; - } - break; - - case ">": - if (lookahead.equals("=")) { - return ">="; - } - if (lookahead.equals(">")) { - return ">>"; - } - break; - - case "!": - if (lookahead.equals("=")) { - return "!="; - } - break; - - case "|": - if (lookahead.equals("|")) { - return "||"; - } - break; - - case "&": - if (lookahead.equals("&")) { - return "&&"; - } - break; - } - - return tok; + public static Address evaluate(Program p, String inputExpression) { + return evaluate(p, null, inputExpression); } /** - * Gets a legitimate address for the specified program as indicated by the string. + * Gets a valid address for the specified program as indicated by the input expression. * @param p the program to use for determining the address. - * @param s string representation of the address desired. + * @param baseAddr the base address to use for relative addressing. + * @param inputExpression string representation of the address desired. * @return the address. Otherwise, return null if the string fails to evaluate - * to a legitimate address. + * to a unique legitimate address. */ - public static Address evaluate(Program p, String s) { - return evaluate(p, null, s); + public static Address evaluate(Program p, Address baseAddr, String inputExpression) { + AddressEvaluator evaluator = new AddressEvaluator(p, true); + try { + return evaluator.parseAsAddress(inputExpression); + } + catch (ExpressionException e) { + return null; + } } /** - * Utility method for creating an Address object from a byte array. The Address object may or may not - * be a legitimate Address in the program's address space. This method is meant to provide a way of - * creating an Address object from a sequence of bytes that can be used for additional tests and - * comparisons. - * - * @param p - program being analyzed. - * @param addrBytes - byte array to use containing the values the address will be constructed from. - * @return - Address object constructed from the addrBytes array. Returns null if the program is null, - * addrBytes is null, or the length of addrBytes does not match the default Pointer size or does not contain - * a valid offset. - * + * Constructs an AddressEvalutor for the given program and in the specified hex/decimal mode. + * @param program the program to use to evaluate expressions into valid addresses. + * @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers. */ - public static Address evaluate(Program p, byte[] addrBytes) { - - boolean isBigEndian = p.getMemory().isBigEndian(); - - int ptrSize = p.getDefaultPointerSize(); - int index = 0; - long offset = 0; - - // Make sure correct # of bytes were passed - if (addrBytes == null || addrBytes.length != ptrSize) { - return null; - } - - /* - * Make sure we account for endianness of the program. - * Computing the number of bits to shift the current byte value - * is different for Little vs. Big Endian. Need to multiply by - * 8 to shift in 1-byte increments. - */ - if (isBigEndian) { - index = 0; - while (index < addrBytes.length) { - offset += (addrBytes[index] & 0xff) << ((addrBytes.length - index - 1) * 8); - index++; - } - } - else { - // Program is LittleEndian - index = addrBytes.length - 1; - while (index >= 0) { - offset += ((addrBytes[index] & 0xff) << (index * 8)); - index--; - } - } - - AddressSpace space = p.getAddressFactory().getDefaultAddressSpace(); - try { - return space.getAddress(offset, true); - } - catch (AddressOutOfBoundsException e) { - return null; - } + public AddressEvaluator(Program program, boolean assumeHex) { + this(program, null, assumeHex); } - private static Object getValueObject(SymbolTable st, AddressFactory af, String tok) { + /** + * Constructs an AdddressEvaluator without a full program. This version will not be able to + * evaluate symbol or memory block names. This is mostly for backwards compatibility. + * @param factory the address factory for creating addresses + * @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers. + */ + public AddressEvaluator(AddressFactory factory, boolean assumeHex) { + this(factory, null, assumeHex); + } - if (st == null || af == null) { - return getValueObject(tok); - } + /** + * Constructs an AddressEvalutor for the given program and in the specified hex/decimal mode. + * @param program the program to use to evaluate expressions into valid addresses. + * @param defaultSpace The address space to use when converting long values into addresses. If + * this value is null, then the default address space will be used. + * @param assumeHex if true, all numeric values are assumed to be hexadecimal numbers. + */ + public AddressEvaluator(Program program, AddressSpace defaultSpace, boolean assumeHex) { + this(program.getAddressFactory(), defaultSpace, assumeHex); + this.programReference = new WeakReference<>(program); + } - try { - return NumericUtilities.parseHexLong(tok); + private AddressEvaluator(AddressFactory factory, AddressSpace defaultSpace, boolean assumeHex) { + super(assumeHex); + this.addressFactory = factory; + this.preferredSpace = defaultSpace; + } + + /** + * Evaluates the given input expression as an address. + * @param input the expression to evaluate + * @return the Address the expression evaluates to + * @throws ExpressionException if the input expression can't be evaluated to a valid, unique + * address. + */ + public Address parseAsAddress(String input) throws ExpressionException { + return this.parseAsRelativeAddress(input, null); + } + + /** + * Evaluates the given input expression as a relative offset that will be added to the given + * base address. + * @param input the expression to evaluate as an offset + * @param baseAddress the base address the evaluted expression will be added to to get the + * resulting address. + * @return the Address after the evaluated offset is added to the given base address. + * @throws ExpressionException if the input expression can't be evaluated to a valid, unique + * address. + */ + public Address parseAsRelativeAddress(String input, Address baseAddress) + throws ExpressionException { + ExpressionValue expressionValue = baseAddress == null ? parse(input) + : parse(input, new AddressExpressionValue(baseAddress)); + + if (expressionValue instanceof AddressExpressionValue addressValue) { + return validateAddressSpace(addressValue.getAddress()); } - catch (NumberFormatException e) { - // ignore + if (expressionValue instanceof LongExpressionValue longValue) { + long offset = longValue.getLongValue(); + AddressSpace space = getAddressSpace(); + try { + return space.getAddressInThisSpaceOnly(offset); + } + catch (AddressOutOfBoundsException e) { + throw new ExpressionException(e.getMessage()); + } } - Address address = af.getAddress(tok); - if (address != null) { + throw new ExpressionException("Expression did not evalute to a long! Got a " + + expressionValue.getClass() + " instead."); + + } + + /** + * Returns the {@link AddressFactory} being used by this address evaluator + * @return the {@link AddressFactory} being used by this address evaluator + */ + public AddressFactory getAddressFactory() { + return addressFactory; + } + + /** + * Sets the {@link AddressSpace} to be used to convert long values into addresses. + * @param space the address space to convert long values into addresses + */ + public void setPreferredAddressSpace(AddressSpace space) { + this.preferredSpace = space; + } + + // checks if the given address's address space is compatible with the preferred address space + private Address validateAddressSpace(Address address) throws ExpressionException { + if (preferredSpace == null) { return address; } - - List globalSymbols = st.getLabelOrFunctionSymbols(tok, null); - if (globalSymbols.size() == 1) { - return globalSymbols.get(0).getAddress(); + AddressSpace space = address.getAddressSpace(); + if (space.equals(preferredSpace)) { + return address; } - return getValueObject(tok); + if (isOverlayRelated(space, preferredSpace)) { + return preferredSpace.getAddress(address.getOffset()); + } + throw new ExpressionException("Selected address space is not compatible with expression!"); } - private static Object getValueObject(String strValue) { - try { - int start = 0; - int radix = 10; - if (strValue.indexOf("0x") == 0) { - start = 2; - radix = 16; - } - strValue = strValue.toLowerCase(); - if (strValue.endsWith("ull") || strValue.endsWith("llu")) { - strValue = strValue.substring(start, strValue.length() - 3); - } - else if (strValue.endsWith("ul") || strValue.endsWith("lu") || strValue.endsWith("ll")) { - strValue = strValue.substring(start, strValue.length() - 2); - } - else if (strValue.endsWith("l") || strValue.endsWith("u")) { - strValue = strValue.substring(start, strValue.length() - 1); - } - else { - strValue = strValue.substring(start); - } - - return (radix == 10) ? NumericUtilities.parseLong(strValue) - : NumericUtilities.parseHexLong(strValue); - } - catch (RuntimeException e) { - // ignore - } - return null; + private boolean isOverlayRelated(AddressSpace space1, AddressSpace space2) { + AddressSpace base1 = getBaseSpace(space1); + AddressSpace base2 = getBaseSpace(space2); + return base1.equals(base2); } - private static Object eval(List list) { - - // first evaluate any grouped expressions - boolean done = false; - while (!done) { - done = true; - for (int i = 0; i < list.size(); i++) { - if (list.get(i) == Operator.LEFT_PAREN) { - done = false; - int end = findMatchingParen(list, i); - if (end < 0) { - return null; - } - Object obj = eval(list.subList(i + 1, end)); - if (obj == null) { - return null; - } - list.subList(i, i + 2).clear(); - list.set(i, obj); - } - } + private AddressSpace getBaseSpace(AddressSpace space) { + if (space instanceof OverlayAddressSpace overlaySpace) { + return overlaySpace.getOverlayedSpace(); } - - //check for leading Minus - if (list.size() > 1 && list.get(0) == Operator.MINUS) { - Object obj = list.get(1); - if (obj instanceof Long) { - obj = -((Long) obj).longValue(); - list.remove(0); - list.set(0, obj); - } - } - - //check for leading ~ - if (list.size() > 1 && list.get(0) == Operator.NOT) { - Object obj = list.get(1); - if (obj instanceof Long) { - obj = ~((Long) obj).longValue(); - list.remove(0); - list.set(0, obj); - } - } - - //check for trailing leading ~ - if (list.size() > 3 && list.get(2) == Operator.NOT) { - Object obj = list.get(3); - if (obj instanceof Long) { - obj = ~((Long) obj).longValue(); - list.remove(2); - list.set(2, obj); - } - } - - // evaluate all SHIFT because they have precedence - if (!evaluateOperator(list, Operator.RIGHTSHIFT, Operator.LEFTSHIFT)) { - return null; - } - - // evaluate all TIMES because they have precedence - if (!evaluateOperator(list, Operator.TIMES, Operator.DIVIDE)) { - return null; - } - - // evaluate Plus and Minus, same precedence, but do plus then minus - if (!evaluateOperator(list, Operator.PLUS, Operator.MINUS)) { - return null; - } - - // evaluate & ^ | - if (!evaluateOperator(list, Operator.AND, null)) { - return null; - } - if (!evaluateOperator(list, Operator.XOR, null)) { - return null; - } - if (!evaluateOperator(list, Operator.OR, null)) { - return null; - } - - if (!evaluateOperator(list, Operator.EQUALS, Operator.NOTEQUALS)) { - return null; - } - - if (!evaluateOperator(list, Operator.LESS, Operator.GREATER)) { - return null; - } - - if (!evaluateOperator(list, Operator.LESSEQUALS, Operator.GREATEREQUALS)) { - return null; - } - - if (!evaluateOperator(list, Operator.LOG_AND, null)) { - return null; - } - - if (!evaluateOperator(list, Operator.LOG_OR, null)) { - return null; - } - - if (list.size() != 1) { - return null; - } - return list.get(0); + return space; } - private static boolean evaluateOperator(List list, Operator op1, Operator op2) { - boolean done; - done = false; - while (!done) { - done = true; - for (int i = 0; i < list.size(); i++) { - Object obj = list.get(i); - if (obj == op1 || obj == op2) { - done = false; - if (i == 0 || i == list.size() - 1) { - return false; - } - Object value = computeValue(list.get(i - 1), (Operator) obj, list.get(i + 1)); - if (value == null) { - return false; - } - list.subList(i, i + 2).clear(); - list.set(i - 1, value); - } - } + private AddressSpace getAddressSpace() { + if (preferredSpace != null) { + return preferredSpace; } - return true; - } - - private static Object computeValue(Object v1, Operator op, Object v2) { - if (op == Operator.TIMES) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() * ((Long) v2).longValue(); - } - } - if (op == Operator.DIVIDE) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() / ((Long) v2).longValue(); - } - } - else if (op == Operator.AND) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() & ((Long) v2).longValue(); - } - } - else if (op == Operator.XOR) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() ^ ((Long) v2).longValue(); - } - } - else if (op == Operator.OR) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() | ((Long) v2).longValue(); - } - } - else if (op == Operator.LEFTSHIFT) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() << ((Long) v2).longValue(); - } - } - else if (op == Operator.RIGHTSHIFT) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() >> ((Long) v2).longValue(); - } - } - else if (op == Operator.PLUS) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() + ((Long) v2).longValue(); - } - else if ((v1 instanceof Address) && (v2 instanceof Long)) { - return ((Address) v1).addWrap(((Long) v2).longValue()); - } - else if ((v1 instanceof Long) && (v2 instanceof Address)) { - return ((Address) v2).addWrap(((Long) v1).longValue()); - } - } - else if (op == Operator.NOT) { - if (v2 instanceof Long) { - return ~(((Long) v2).longValue()); - } - else if (v2 instanceof Address) { - return ((Address) v2).getNewAddress(~(((Long) v2).longValue())); - } - } - else if (op == Operator.MINUS) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() - ((Long) v2).longValue(); - } - else if ((v1 instanceof Address) && (v2 instanceof Long)) { - return ((Address) v1).subtractWrap(((Long) v2).longValue()); - } - else if ((v1 instanceof Address) && (v2 instanceof Address)) { - return ((Address) v1).subtract((Address) v2); - } - } - else if (op == Operator.EQUALS) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff == 0L ? 1L : 0L; - } - } - else if (op == Operator.NOTEQUALS) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff != 0L ? 1L : 0L; - } - } - else if (op == Operator.LESSEQUALS) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff <= 0L ? 1L : 0L; - } - } - else if (op == Operator.GREATEREQUALS) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff >= 0L ? 1L : 0L; - } - } - else if (op == Operator.LESS) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff < 0L ? 1L : 0L; - } - } - else if (op == Operator.GREATER) { - Long diff = getDifference(v1, v2); - if (diff != null) { - return diff > 0L ? 1L : 0L; - } - } - else if (op == Operator.LOG_AND) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - boolean test = (((Long) v1).longValue()) != 0 && (((Long) v2).longValue()) != 0; - return test ? 1L : 0L; - } - } - else if (op == Operator.LOG_OR) { - if ((v1 instanceof Long) && (v2 instanceof Long)) { - boolean test = (((Long) v1).longValue()) != 0 || (((Long) v2).longValue()) != 0; - return test ? 1L : 0L; - } - } - return null; - } - - private static Long getDifference(Object v1, Object v2) { - if ((v1 instanceof Address) && (v2 instanceof Long)) { - return ((Address) v1).subtractWrap(((Long) v2).longValue()).getOffset(); - } - else if ((v1 instanceof Address) && (v2 instanceof Address)) { - return ((Address) v1).subtract((Address) v2); - } - else if ((v1 instanceof Long) && (v2 instanceof Long)) { - return ((Long) v1).longValue() - ((Long) v2).longValue(); - } - return null; - } - - private static int findMatchingParen(List list, int index) { - int depth = 1; - for (int j = index + 1; j < list.size(); j++) { - Object obj = list.get(j); - if (obj == Operator.LEFT_PAREN) { - depth++; - } - else if (obj == Operator.RIGHT_PAREN) { - if (--depth == 0) { - return j; - } - } - } - return -1; - } -} - -class Operator { - static Operator PLUS = new Operator("+"); - static Operator MINUS = new Operator("-"); - static Operator TIMES = new Operator("*"); - static Operator DIVIDE = new Operator("/"); - static Operator AND = new Operator("&"); - static Operator OR = new Operator("|"); - static Operator NOT = new Operator("~"); - static Operator XOR = new Operator("^"); - static Operator LEFTSHIFT = new Operator("<<"); - static Operator RIGHTSHIFT = new Operator(">>"); - static Operator LEFT_PAREN = new Operator("("); - static Operator RIGHT_PAREN = new Operator(")"); - static Operator LOG_OR = new Operator("||"); - static Operator LOG_AND = new Operator("&&"); - static Operator EQUALS = new Operator("=="); - static Operator NOTEQUALS = new Operator("!="); - static Operator LESS = new Operator("<"); - static Operator GREATER = new Operator(">"); - static Operator LESSEQUALS = new Operator("<="); - static Operator GREATEREQUALS = new Operator(">="); - - final String name; - - private Operator(String name) { - this.name = name; + return addressFactory.getDefaultAddressSpace(); } @Override - public String toString() { - return name; + protected ExpressionValue evaluateSymbol(String input) { + Address address = addressFactory.getAddress(input); + if (address != null) { + return new AddressExpressionValue(address); + } + + Program program = getProgram(); + if (program != null) { + return getAddressForProgram(program, input); + } + + return null; } - /** - * Gets the static object implementation of an operator. - * @param tok token string for the operator - * @return the static operator object. - */ - public static Operator getOperator(String tok) { - if (tok.equals("+")) { - return PLUS; + private ExpressionValue getAddressForProgram(Program program, String input) { + Address address = getAddressForSymbol(program, input); + if (address == null) { + address = getAddressFromMemoryMap(program, input); } - if (tok.equals("&")) { - return AND; + + return address == null ? null : new AddressExpressionValue(address); + } + + private Address getAddressFromMemoryMap(Program program, String input) { + Memory memory = program.getMemory(); + MemoryBlock block = memory.getBlock(input); + if (block != null) { + return block.getStart(); } - if (tok.equals("|")) { - return OR; + return null; + } + + private Address getAddressForSymbol(Program program, String input) { + SymbolPath symbolPath = new SymbolPath(input); + String symbolName = symbolPath.getName(); + SymbolPath parent = symbolPath.getParent(); + + Namespace namespace = null; + + if (parent != null) { + namespace = getParentNamespace(program, parent); + if (namespace == null) { + // there was a namespace specified, but not uniquely found, so can't resolve. + return null; + } } - if (tok.equals("^")) { - return XOR; + SymbolTable symbolTable = program.getSymbolTable(); + List symbols = symbolTable.getLabelOrFunctionSymbols(symbolName, namespace); + if (symbols.size() == 1) { + return symbols.get(0).getAddress(); } - else if (tok.equals("-")) { - return MINUS; + return null; + } + + private Namespace getParentNamespace(Program program, SymbolPath path) { + if (path == null) { + return null; } - else if (tok.equals("~")) { - return NOT; + List spaces = NamespaceUtils.getNamespaceByPath(program, null, path.getPath()); + if (spaces.size() == 1) { + return spaces.get(0); } - else if (tok.equals("*")) { - return TIMES; - } - else if (tok.equals("/")) { - return DIVIDE; - } - else if (tok.equals(")")) { - return RIGHT_PAREN; - } - else if (tok.equals("(")) { - return LEFT_PAREN; - } - else if (tok.equals("<<")) { - return LEFTSHIFT; - } - else if (tok.equals(">>")) { - return RIGHTSHIFT; - } - else if (tok.equals("==")) { - return EQUALS; - } - else if (tok.equals("!=")) { - return NOTEQUALS; - } - else if (tok.equals("<")) { - return LESS; - } - else if (tok.equals(">")) { - return GREATER; - } - else if (tok.equals("<=")) { - return LESSEQUALS; - } - else if (tok.equals(">=")) { - return GREATEREQUALS; - } - else if (tok.equals("||")) { - return LOG_OR; - } - else if (tok.equals("&&")) { - return LOG_AND; + return null; + } + + private Program getProgram() { + if (programReference != null) { + return programReference.get(); } return null; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressExpressionValue.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressExpressionValue.java new file mode 100644 index 0000000000..143931d8a7 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/AddressExpressionValue.java @@ -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(); + } + +} diff --git a/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java b/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java index 22f1645005..781ece80ee 100644 --- a/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java +++ b/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java @@ -27,6 +27,7 @@ import java.util.Iterator; import org.bouncycastle.util.Arrays; +import generic.expressions.ExpressionEvaluator; import generic.jar.ResourceFile; import ghidra.app.plugin.core.datamgr.util.DataTypeArchiveUtility; import ghidra.app.script.GhidraScript; @@ -35,7 +36,6 @@ import ghidra.app.util.cparser.C.CParserUtils.CParseResults; import ghidra.app.util.cparser.CPP.*; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.FileDataTypeManager; -import ghidra.program.util.AddressEvaluator; import ghidra.util.Msg; public class CreateAVR8GDTArchiveScript extends GhidraScript { @@ -230,7 +230,7 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript { continue; } - lvalue = AddressEvaluator.evaluateToLong(expandValue); + lvalue = ExpressionEvaluator.evaluateToLong(expandValue); if (lvalue == null) { continue; } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemoryMapPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemoryMapPluginScreenShots.java index 8122eb972d..3e288f9db2 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemoryMapPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/MemoryMapPluginScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -140,7 +140,7 @@ public class MemoryMapPluginScreenShots extends GhidraScreenShotGenerator { DialogComponentProvider dialog = getDialog(); GhidraComboBox comboBox = (GhidraComboBox) getInstanceField("comboBox", dialog); selectItem(comboBox, "Byte Mapped"); - + runSwing(() -> dialog.setStatusText("")); captureDialog(); drawRectangleAround(comboBox, Palette.GREEN, 10);