diff --git a/Ghidra/Features/Base/data/ExtensionPoint.manifest b/Ghidra/Features/Base/data/ExtensionPoint.manifest index 711f68ce96..75640c0c6f 100644 --- a/Ghidra/Features/Base/data/ExtensionPoint.manifest +++ b/Ghidra/Features/Base/data/ExtensionPoint.manifest @@ -3,6 +3,7 @@ Demangler Exporter FieldFactory FieldMouseHandler +FileSystem StringHandler Loader SourceLanguage diff --git a/Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java b/Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java index 1ab0709218..bdec784ef7 100644 --- a/Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/YaraGhidraGUIScript.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,7 +101,7 @@ public class YaraGhidraGUIScript extends GhidraScript { state.getTool().showDialog(dialog); // Finally, load whatever instructions are selected in the listing. - dialog.loadInstructions(plugin); + dialog.loadInstructions(); } /********************************************************************************************* diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Instruction_Patterns.htm b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Instruction_Patterns.htm index 6c01549e03..681a8fdbfd 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Instruction_Patterns.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Search/Search_Instruction_Patterns.htm @@ -38,8 +38,9 @@ on an item in the table to mask it from the final search string.

Color-coding is used to indicate the code - unit type. Instructions are displayed in - blue, data items are tan.

+ unit type. + Instructions are displayed in blue, + data items are tan.

@@ -52,10 +53,12 @@

These tools provide ways to manipulate the Instruction Table and are discussed in detail below:

+

Provided by: InstructionSearchPlugin

+
+
+
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatterns.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatterns.png index 7f2ac1a55d..786a8929cc 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatterns.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatterns.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png index 4df41f423b..27a2207234 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTable.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTableToolbar.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTableToolbar.png index 29b4eea9fc..115e6b33e0 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTableToolbar.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsInstructionTableToolbar.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsPreviewTable.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsPreviewTable.png index c999dcee5d..6bc8467a59 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsPreviewTable.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionPatternsPreviewTable.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png index 5bc9bc711c..e1503744a3 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/Search/images/SearchInstructionsManualSearchDialog.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchApi.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchApi.java index 35c48ea3ca..22f6cf219b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchApi.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchApi.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. @@ -31,19 +31,23 @@ import ghidra.util.task.TaskMonitor; /** * API for users who wish to perform instruction searching without the GUI. - * + *

* Limitations: - * 1) Searches may only be performed on a single program. - * 2) Only a single address range may be searched for. - * + *

    + *
  1. 1) Searches may only be performed on a single program. + *
  2. 2) Only a single address range may be searched for. + *
+ *

* Results: * Can be returned in 2 ways: - * 1) As a list of addresses representing the location of search matches. - * 2) As a string (either binary or hex) representing the search string to be used. - * The latter results option is useful if using another tool to perform the search (ie yara). - * + *

    + *
  1. 1) As a list of addresses representing the location of search matches. + *
  2. 2) As a string (either binary or hex) representing the search string to be used. + *
+ * The latter results option is useful if using another tool to perform the search (i.e., Yara). + *

* Extending: - * This class may be extended to provide an api for specific searching formats. There is + * This class may be extended to provide an API for specific searching formats. There is * currently an extension for Yara: {@link InstructionSearchApi_Yara}. * */ @@ -54,10 +58,10 @@ public class InstructionSearchApi { * filtering of results is performed; all matches regardless of operand type will be * returned. * - * @param program - * @param addressRange + * @param program the program + * @param addressRange the addresses * @return a list of addresses indicating starting positions of matches. - * @throws InvalidInputException + * @throws InvalidInputException if there are no code units in the given addresses */ public final List

search(Program program, AddressRange addressRange) throws InvalidInputException { @@ -84,12 +88,12 @@ public class InstructionSearchApi { * Searches the given program for the instructions specified by the given address range, with * masking set according to the given {@link MaskSettings} object * - * @param program - * @param addressRange - * @param maskSettings + * @param program the program + * @param addressRange the addresses + * @param maskSettings the mask settings * * @return a list of addresses indicating starting positions of matches. - * @throws InvalidInputException + * @throws InvalidInputException if there are no code units in the given addresses */ public final List
search(Program program, AddressRange addressRange, MaskSettings maskSettings) throws InvalidInputException { @@ -127,10 +131,10 @@ public class InstructionSearchApi { /** * Returns a binary string representing the bytes in the address range provided. * - * @param program - * @param addressRange - * @return - * @throws InvalidInputException + * @param program the program + * @param addressRange the addresses + * @return the search string + * @throws InvalidInputException if there are no code units in the given addresses */ public final String getBinarySearchString(Program program, AddressRange addressRange) throws InvalidInputException { @@ -144,14 +148,15 @@ public class InstructionSearchApi { /** * Returns a hex version of the bytes representing the address range given. * - * @param program - * @param addressRange - * @return - * @throws InvalidInputException + * @param program the program + * @param addressRange the addresses + * @return the search string + * @throws InvalidInputException if there are no code units in the given addresses */ public final String getHexSearchString(Program program, AddressRange addressRange) throws InvalidInputException { - return InstructionSearchUtils.toHexNibblesOnly(getBinarySearchString(program, addressRange)).toString(); + String searchString = getBinarySearchString(program, addressRange); + return InstructionSearchUtils.toHexNibblesOnly(searchString).toString(); } /** @@ -160,11 +165,11 @@ public class InstructionSearchApi { * * Note: Masked bits will be represented by a '.' character. * - * @param maskSettings - * @param addressRange - * @param maskSettings - * @return - * @throws InvalidInputException + * @param program the program + * @param addressRange the addresses + * @param maskSettings the mask settings + * @return the search string + * @throws InvalidInputException if there are no code units in the given addresses */ public final String getBinarySearchString(Program program, AddressRange addressRange, MaskSettings maskSettings) throws InvalidInputException { @@ -190,16 +195,16 @@ public class InstructionSearchApi { /** * Returns a hex version of the bytes representing the address range given. * - * @param program - * @param addressRange - * @param maskSettings - * @return - * @throws InvalidInputException + * @param program the program + * @param addressRange the addresses + * @param maskSettings the mask settings + * @return the search string + * @throws InvalidInputException if there are no code units in the given addresses */ public final String getHexSearchString(Program program, AddressRange addressRange, MaskSettings maskSettings) throws InvalidInputException { - return InstructionSearchUtils.toHexNibblesOnly( - getBinarySearchString(program, addressRange, maskSettings)).toString(); + String searchString = getBinarySearchString(program, addressRange, maskSettings); + return InstructionSearchUtils.toHexNibblesOnly(searchString).toString(); } /** @@ -224,6 +229,7 @@ public class InstructionSearchApi { * instructions would be impossible otherwise. * * @param bytes binary or hex string representing the bytes to be loaded + * @param tool the tool */ public void loadInstructions(String bytes, PluginTool tool) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java index 3189ec4c77..4179a7c9df 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java @@ -68,7 +68,7 @@ import ghidra.util.task.TaskMonitorComponent; //@formatter:on public class InstructionSearchPlugin extends ProgramPlugin { - final static String SEARCH_ACTION_NAME = "Search Instruction Patterns"; + public static final String SEARCH_ACTION_NAME = "Search Instruction Patterns"; private TaskMonitor taskMonitor; @@ -201,8 +201,7 @@ public class InstructionSearchPlugin extends ProgramPlugin { } /** - * Returns true if the number of instructions selected is less or equal to - * MAX_SELECTION_SIZE. + * Returns true if the number of instructions selected is less or equal to MAX_SELECTION_SIZE. * * @param selection the program selection * @return true if the selection size is valid @@ -217,7 +216,7 @@ public class InstructionSearchPlugin extends ProgramPlugin { /** * Returns true if the user has selected one and only one range of * instructions. - * + *

* If there are multiple ranges, this could be for two reasons: 1) the user * has (via the mouse) selected more than one set of address ranges, or 2) * the user selects a single region but that region spans memory blocks; @@ -227,7 +226,7 @@ public class InstructionSearchPlugin extends ProgramPlugin { * * @param selection the program selection * @return true if the selection range is valid - * @throws InvalidInputException + * @throws InvalidInputException if the given selection is invalid */ private boolean isSelectionRangeValid(ProgramSelection selection) throws InvalidInputException { Set blockNames = new HashSet<>(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/InstructionSearchData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/InstructionSearchData.java index 11f21c083f..e70844a14c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/InstructionSearchData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/InstructionSearchData.java @@ -17,7 +17,6 @@ package ghidra.app.plugin.core.instructionsearch.model; import java.util.*; -import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.instructionsearch.ui.AbstractInstructionTable.OperandState; import ghidra.app.plugin.core.instructionsearch.ui.InstructionSearchDialog; import ghidra.app.plugin.core.instructionsearch.ui.InstructionTable; @@ -29,6 +28,7 @@ import ghidra.program.model.lang.OperandType; import ghidra.program.model.listing.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.util.ProgramLocation; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.exception.InvalidInputException; @@ -38,6 +38,7 @@ import ghidra.util.task.*; * This is the data model that {@link InstructionSearchDialog} instances use * when building their displays. */ +@SuppressWarnings("deprecation") public class InstructionSearchData extends Observable { // This is the entire set of instructions that will be searched on when/if the user @@ -68,10 +69,29 @@ public class InstructionSearchData extends Observable { table.getModel().addTableModelListener(e -> applyMasks(table)); } + /** + * Deletes the instruction at the given index + * @param table the table + * @param index the index + */ + public void deleteInstruction(InstructionTable table, int index) { + instructions.remove(index); + InstructionTableModel model = (InstructionTableModel) table.getModel(); + + @SuppressWarnings("rawtypes") + Vector data = model.getDataVector(); + data.remove(index); + + model.fireTableRowsDeleted(index, index); + + modelChanged(UpdateType.UPDATE); + } + /** * This method ensures that all mask settings in the dialog are applied to * the {@link #instructions} list. * + * @param table the table * @see InstructionSearchData */ public void applyMasks(InstructionTable table) { @@ -89,20 +109,18 @@ public class InstructionSearchData extends Observable { * * @param program the current program * @param addressRange the addresses to load instructions for - * @throws InvalidInputException if there's an error parsing the - * instructions + * @throws InvalidInputException if there's an error parsing the instructions */ public void load(Program program, AddressRange addressRange) throws InvalidInputException { - // first clear out any current instructions. - instructions.clear(); - // Do some initial checks on the program and addresses we want to load instructions // for. If these are invalid, no need to proceed. if (program == null || addressRange == null || addressRange.getLength() == 0) { return; } + instructions.clear(); + // Now we have to use the sleigh logger to parse each of the code units in the // requested address set. Listing listing = program.getListing(); @@ -149,69 +167,6 @@ public class InstructionSearchData extends Observable { modelChanged(UpdateType.RELOAD); } - private class LoadInstructionsTask extends Task { - - private Program program; - private AddressSetView addresses; - - public LoadInstructionsTask(Program program, AddressSetView addresses) { - super("Loading Instructions", true, true, true); - this.program = program; - this.addresses = addresses; - } - - @Override - public void run(TaskMonitor monitor) throws CancelledException { - - monitor.setIndeterminate(true); - - Listing listing = program.getListing(); - CodeUnitIterator it = listing.getCodeUnits(addresses, true); - while (it.hasNext()) { - if (monitor.isCancelled()) { - return; - } - - CodeUnit cu = it.next(); - - InstructionMetadata instructionMetadata; - - // If this CU is an instruction, we can use the Sleigh debug logger to build the - // mask info. If not, we don't need to create anything complex for masking - it's - // either on or off. - if (cu instanceof Instruction) { - SleighDebugLogger logger = - new SleighDebugLogger(program, cu.getAddress(), SleighDebugMode.VERBOSE); - if (logger.parseFailed()) { - Msg.showError(this, null, "Parsing error", - "Error parsing instruction: " + cu.toString()); - return; - } - - instructionMetadata = getInstructionMetadata(logger, cu); - if (instructionMetadata != null) { - instructions.add(instructionMetadata); - } - processOperands(logger, cu, instructionMetadata); - - } - else if (cu instanceof Data) { - try { - instructionMetadata = getInstructionMetadata(cu); - if (instructionMetadata != null) { - instructions.add(instructionMetadata); - } - } - catch (InvalidInputException e) { - Msg.showError(this, null, "Parsing error", - "Error parsing data: " + cu.toString()); - return; - } - } - } - } - } - /** * Clears out the instruction list in this model, and fires off a * notification to subscribers. @@ -390,15 +345,16 @@ public class InstructionSearchData extends Observable { } // Make sure the column isn't out of bounds. - if (col >= instructions.get(row).getOperands().size()) { + InstructionMetadata metadata = instructions.get(row); + List operands = metadata.getOperands(); + if (col >= operands.size()) { return; } - instructions.get(row) - .getOperands() - .get(col) - .setMasked( - table.getCellData(row, col + 1).getState().equals(OperandState.MASKED)); + OperandMetadata operandMetadata = operands.get(col); + InstructionTableDataObject data = table.getCellData(row, col + 1); + boolean isMasked = data.getState().equals(OperandState.MASKED); + operandMetadata.setMasked(isMasked); } /** @@ -425,8 +381,8 @@ public class InstructionSearchData extends Observable { * * @return the mask container object */ - private MaskContainer getAllMasks() { + private MaskContainer getAllMasks() { List masks = new ArrayList(); List values = new ArrayList(); @@ -510,12 +466,12 @@ public class InstructionSearchData extends Observable { byte[] value, boolean instruction) { MaskContainer masks = new MaskContainer(mask, value); - InstructionMetadata instructionMetadata = new InstructionMetadata(masks); + InstructionMetadata metadata = new InstructionMetadata(masks); - instructionMetadata.setAddr(codeUnit.getAddress()); - instructionMetadata.setTextRep(codeUnit.getMnemonicString()); - instructionMetadata.setIsInstruction(instruction); - return instructionMetadata; + metadata.setAddr(codeUnit.getAddress()); + metadata.setTextRep(codeUnit.getMnemonicString()); + metadata.setIsInstruction(instruction); + return metadata; } /** @@ -586,20 +542,24 @@ public class InstructionSearchData extends Observable { if (instruction == null) { return null; } - if (instruction.getMaskContainer() == null) { - return null; - } - if (instruction.getMaskContainer().getMask() == null || - instruction.getMaskContainer().getValue() == null) { + + MaskContainer maskContainer = instruction.getMaskContainer(); + if (maskContainer == null) { return null; } - // Now Create the mask/value arrays. Populating these is the main point of this method; these + byte[] mask = maskContainer.getMask(); + byte[] maskValue = maskContainer.getValue(); + if (mask == null || maskValue == null) { + return null; + } + + // Now Create the mask/value arrays. Populating these is the main point of this method; they // will be placed in the return object when we're done. Note that they're initialized to // be the size of the mask/value arrays in the given instruction; these are guaranteed // to be the correct size (if they're not, there's a serious problem). - byte[] tempMask = new byte[instruction.getMaskContainer().getMask().length]; - byte[] tempValue = new byte[instruction.getMaskContainer().getValue().length]; + byte[] tempMask = new byte[mask.length]; + byte[] tempValue = new byte[maskValue.length]; ////////////////// // MNEMONIC @@ -609,8 +569,8 @@ public class InstructionSearchData extends Observable { // mnemonic portion of the arrays set to 0 (they were set to 0 above, when initialized). If // not masked, then we need to put the actual mnemonic bytes in the temp/value arrays. if (!instruction.isMasked()) { - tempValue = instruction.getMaskContainer().getValue(); - tempMask = instruction.getMaskContainer().getMask(); + tempValue = maskValue; + tempMask = mask; } ////////////////// @@ -621,38 +581,32 @@ public class InstructionSearchData extends Observable { // mask/value to the main arrays depending on the mask setting. for (OperandMetadata operand : instruction.getOperands()) { - // If masked, then just leave the value of the bits at 0. Continue to the next - // operand. + // If masked, then just leave the value of the bits at 0. Continue to the next operand. if (operand.isMasked()) { continue; } // Now do some due diligence with null checks... - if (operand.getMaskContainer().getValue() == null || - operand.getMaskContainer().getMask() == null || tempValue == null || - tempMask == null) { + maskValue = operand.getMaskContainer().getValue(); + mask = operand.getMaskContainer().getMask(); + if (maskValue == null || mask == null || tempValue == null || tempMask == null) { continue; } // Everything looks good, so apply the operand masks. - tempValue = InstructionSearchUtils.byteArrayOr(tempValue, - operand.getMaskContainer().getValue()); - tempMask = - InstructionSearchUtils.byteArrayOr(tempMask, operand.getMaskContainer().getMask()); + tempValue = InstructionSearchUtils.byteArrayOr(tempValue, maskValue); + tempMask = InstructionSearchUtils.byteArrayOr(tempMask, mask); } - // Now create a new struct for the mask and return to the caller. - MaskContainer result; try { - result = new MaskContainer(tempMask, tempValue); + return new MaskContainer(tempMask, tempValue); } catch (IllegalArgumentException e) { // If we're here, then there's a problem with the mask/value arrays we used to - // create the mask container. Just return null. + // create the mask container. + Msg.error(this, e.getMessage(), e); return null; } - - return result; } /** @@ -673,8 +627,8 @@ public class InstructionSearchData extends Observable { // Now figure out the size of the final array we need to construct. We // could use ArrayList objects instead but that would be too inefficient. int totalLength = 0; - for (int i = 0; i < values.size(); i++) { - totalLength += values.get(i).length; + for (byte[] value : values) { + totalLength += value.length; } // This takes the masks and values from each command and concats them together to form @@ -704,66 +658,53 @@ public class InstructionSearchData extends Observable { /** * Searches through instructions in the given program for a specific byte - * pattern. If found, returns the instruction. i + * pattern. If found, returns the instruction. * - * @param program the program to search + * @param location the current program location * @param searchBounds the addresses to search - * @param taskMonitor the task monitor * @param forwardSearch if true, search through addresses forward + * @param monitor the task monitor * @throws IllegalArgumentException if there's a problem parsing addresses * @return the instruction, or null if not found */ - public InstructionMetadata search(ProgramPlugin plugin, AddressRange searchBounds, - TaskMonitor taskMonitor, boolean forwardSearch) { + public InstructionMetadata search(ProgramLocation location, AddressRange searchBounds, + boolean forwardSearch, TaskMonitor monitor) { - if (plugin == null || plugin.getCurrentProgram() == null) { - throw new IllegalArgumentException("Program provided to search is null"); - } + monitor = TaskMonitor.dummyIfNull(monitor); + Objects.requireNonNull(location); - // Do a quick check to make sure the search bounds are within the bounds of the - // program. - if (searchBounds.getMinAddress() - .compareTo( - plugin.getCurrentProgram().getMinAddress()) < 0 || - searchBounds.getMaxAddress() - .compareTo( - plugin.getCurrentProgram().getMaxAddress()) > 0) { + Program program = location.getProgram(); + + // make sure the search bounds are within the bounds of the program + + if (searchBounds.getMinAddress().compareTo(program.getMinAddress()) < 0 || + searchBounds.getMaxAddress().compareTo(program.getMaxAddress()) > 0) { throw new IllegalArgumentException( "Search bounds are not valid; must be within the bounds of the program."); } - MaskContainer maskContainer = this.getAllMasks(); - + MaskContainer maskContainer = getAllMasks(); if (InstructionSearchUtils.containsOnBit(maskContainer.getMask())) { if (forwardSearch) { - return searchForward(plugin, searchBounds, taskMonitor, maskContainer); + return searchForward(location, searchBounds, maskContainer, monitor); } - return searchBackward(plugin, searchBounds, taskMonitor, maskContainer); + return searchBackward(location, searchBounds, maskContainer, monitor); } return null; } - /** - * Searches for a specific byte pattern in the positive direction. - * - * @param plugin the instruction pattern search plugin - * @param searchBounds the addresses to search - * @param taskMonitor the task monitor - * @param maskContainer the bytes to search for - * @return the instruction, or null if not found - */ - private InstructionMetadata searchForward(ProgramPlugin plugin, AddressRange searchBounds, - TaskMonitor taskMonitor, MaskContainer maskContainer) { + private InstructionMetadata searchForward(ProgramLocation location, AddressRange searchBounds, + MaskContainer maskContainer, TaskMonitor monitor) { Address startAddress = searchBounds.getMinAddress(); Address endAddress = searchBounds.getMaxAddress(); - Address currentPosition = plugin.getProgramLocation().getByteAddress().next(); + Address currentPosition = location.getByteAddress().next(); - taskMonitor.setShowProgressValue(false);// no need to show the number of bytes - taskMonitor.setProgress(0); + monitor.setShowProgressValue(false);// no need to show the number of bytes + monitor.setProgress(0); // The maximum value for the monitor is the number of bytes to be checked - this will // NOT always be the size of the range passed-in. If the cursor is in the middle of @@ -773,7 +714,7 @@ public class InstructionSearchData extends Observable { if (currentPosition.compareTo(searchBounds.getMinAddress()) > 0) { max = searchBounds.getMaxAddress().subtract(currentPosition); } - taskMonitor.setMaximum(max); + monitor.setMaximum(max); // Move the cursor to the beginning of the range if it is currently short of it. We don't // want to search for any addresses that aren't in the search bounds. @@ -781,14 +722,15 @@ public class InstructionSearchData extends Observable { currentPosition = startAddress; } + Program program = location.getProgram(); while (currentPosition.compareTo(endAddress) < 0) { // Search program memory for the given mask and val. - currentPosition = plugin.getCurrentProgram() - .getMemory() - .findBytes(currentPosition, - endAddress, maskContainer.getValue(), maskContainer.getMask(), true, - taskMonitor); + Memory memory = program.getMemory(); + byte[] mask = maskContainer.getMask(); + byte[] maskValue = maskContainer.getValue(); + currentPosition = + memory.findBytes(currentPosition, endAddress, maskValue, mask, true, monitor); // If no match was found, currentPosition will be null. if (currentPosition == null) { @@ -796,8 +738,7 @@ public class InstructionSearchData extends Observable { } // Otherwise construct a new entry to put in our results table. - MaskContainer masks = - new MaskContainer(maskContainer.getMask(), maskContainer.getValue()); + MaskContainer masks = new MaskContainer(mask, maskValue); InstructionMetadata temp = new InstructionMetadata(masks); temp.setAddr(currentPosition); @@ -807,24 +748,15 @@ public class InstructionSearchData extends Observable { return null; } - /** - * Searches for a specific byte pattern in the reverse direction. - * - * @param plugin the instruction pattern search plugin - * @param searchBounds the addresses to search - * @param taskMonitor the task monitor - * @param maskContainer the bytes to search for - * @return the instruction, or null if not found - */ - private InstructionMetadata searchBackward(ProgramPlugin plugin, AddressRange searchBounds, - TaskMonitor taskMonitor, MaskContainer maskContainer) { + private InstructionMetadata searchBackward(ProgramLocation location, AddressRange searchBounds, + MaskContainer maskContainer, TaskMonitor monitor) { Address startAddress = searchBounds.getMaxAddress(); Address endAddress = searchBounds.getMinAddress(); - Address currentPosition = plugin.getProgramLocation().getByteAddress().previous(); + Address currentPosition = location.getByteAddress().previous(); - taskMonitor.setShowProgressValue(false); - taskMonitor.setProgress(0); + monitor.setShowProgressValue(false); + monitor.setProgress(0); // The maximum value for the monitor is the number of bytes to be checked - this will // NOT always be the size of the range passed-in. If the cursor is in the middle of @@ -834,7 +766,7 @@ public class InstructionSearchData extends Observable { if (currentPosition.compareTo(searchBounds.getMaxAddress()) < 0) { max = currentPosition.subtract(searchBounds.getMinAddress()); } - taskMonitor.setMaximum(max); + monitor.setMaximum(max); // Move the cursor to the end of the range if it is currently past it. We don't // want to search for any addresses that aren't in the search bounds. @@ -842,14 +774,15 @@ public class InstructionSearchData extends Observable { currentPosition = startAddress; } + Program program = location.getProgram(); while (currentPosition.compareTo(endAddress) > 0) { // Search program memory for the given mask and val. - currentPosition = plugin.getCurrentProgram() - .getMemory() - .findBytes(currentPosition, - endAddress, maskContainer.getValue(), maskContainer.getMask(), false, - taskMonitor); + Memory memory = program.getMemory(); + byte[] mask = maskContainer.getMask(); + byte[] maskValue = maskContainer.getValue(); + currentPosition = memory.findBytes(currentPosition, endAddress, maskValue, mask, false, + monitor); // If no match was found, currentPosition will be null. if (currentPosition == null) { @@ -857,8 +790,7 @@ public class InstructionSearchData extends Observable { } // Otherwise construct a new entry to put in our results table. - MaskContainer masks = - new MaskContainer(maskContainer.getMask(), maskContainer.getValue()); + MaskContainer masks = new MaskContainer(mask, maskValue); InstructionMetadata temp = new InstructionMetadata(masks); temp.setAddr(currentPosition); @@ -930,4 +862,68 @@ public class InstructionSearchData extends Observable { return searchResults; } + + private class LoadInstructionsTask extends Task { + + private Program program; + private AddressSetView addresses; + + public LoadInstructionsTask(Program program, AddressSetView addresses) { + super("Loading Instructions", true, true, true); + this.program = program; + this.addresses = addresses; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + monitor.setIndeterminate(true); + + Listing listing = program.getListing(); + CodeUnitIterator it = listing.getCodeUnits(addresses, true); + while (it.hasNext()) { + if (monitor.isCancelled()) { + return; + } + + CodeUnit cu = it.next(); + + InstructionMetadata instructionMetadata; + + // If this CU is an instruction, we can use the Sleigh debug logger to build the + // mask info. If not, we don't need to create anything complex for masking - it's + // either on or off. + if (cu instanceof Instruction) { + SleighDebugLogger logger = + new SleighDebugLogger(program, cu.getAddress(), SleighDebugMode.VERBOSE); + if (logger.parseFailed()) { + Msg.showError(this, null, "Parsing error", + "Error parsing instruction: " + cu.toString()); + return; + } + + instructionMetadata = getInstructionMetadata(logger, cu); + if (instructionMetadata != null) { + instructions.add(instructionMetadata); + } + processOperands(logger, cu, instructionMetadata); + + } + else if (cu instanceof Data) { + try { + instructionMetadata = getInstructionMetadata(cu); + if (instructionMetadata != null) { + instructions.add(instructionMetadata); + } + } + catch (InvalidInputException e) { + Msg.showError(this, null, "Parsing error", + "Error parsing data: " + cu.toString()); + return; + } + } + } + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/MaskContainer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/MaskContainer.java index ad33dfd967..e16786f7d4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/MaskContainer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/model/MaskContainer.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,6 +16,7 @@ package ghidra.app.plugin.core.instructionsearch.model; import ghidra.app.plugin.core.instructionsearch.util.InstructionSearchUtils; +import ghidra.util.Msg; import ghidra.util.exception.InvalidInputException; /** @@ -26,12 +27,6 @@ public class MaskContainer { private byte[] mask; private byte[] value; - /** - * - * @param mask - * @param value - * @throws InvalidInputException - */ public MaskContainer(byte[] mask, byte[] value) throws IllegalArgumentException { // Mask and value arrays must be the same size, and not be null. @@ -48,79 +43,52 @@ public class MaskContainer { this.value = value; } - /** - * @return the mask - */ public byte[] getMask() { return mask; } - /** - * - * @return - */ public String getMaskAsBinaryString() { StringBuilder str = new StringBuilder(); - for (int i = 0; i < mask.length; i++) { - str.append(InstructionSearchUtils.toBinaryString(mask[i])); + for (byte element : mask) { + str.append(InstructionSearchUtils.toBinaryString(element)); } return str.toString(); } - /** - * @param mask the mask to set - */ public void setMask(byte[] mask) { this.mask = mask; } - /** - * @return the value - */ public byte[] getValue() { return value; } - /** - * - * @return - */ public String getValueAsBinaryString() { StringBuilder str = new StringBuilder(); - for (int i = 0; i < value.length; i++) { - str.append(InstructionSearchUtils.toBinaryString(value[i])); + for (byte element : value) { + str.append(InstructionSearchUtils.toBinaryString(element)); } return str.toString(); } - /** - * @param value the value to set - */ public void setValue(byte[] value) { this.value = value; } - /** - * Returns the bytes and masking merged together, as a binary string. - * - * @param mask - * @param value - * @return list containing the value (index 0) and mask (index 1). - */ public String toBinaryString() { StringBuilder valueString = new StringBuilder(); StringBuilder maskString = new StringBuilder(); - for (int i = 0; i < value.length; i++) { - valueString.append(InstructionSearchUtils.toBinaryString(value[i])); + for (byte element : value) { + valueString.append(InstructionSearchUtils.toBinaryString(element)); } - for (int i = 0; i < mask.length; i++) { - maskString.append(InstructionSearchUtils.toBinaryString(mask[i])); + for (byte element : mask) { + maskString.append(InstructionSearchUtils.toBinaryString(element)); } String combinedString = ""; @@ -129,7 +97,7 @@ public class MaskContainer { maskString.toString()); } catch (InvalidInputException e) { - e.printStackTrace(); + Msg.error(this, e.getMessage(), e); } return combinedString; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java index d15f6cd01e..fa82e32987 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java @@ -35,8 +35,7 @@ import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.app.util.query.TableService; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramSelection; import ghidra.util.*; @@ -46,21 +45,8 @@ import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskMonitor; /** - * The GUI component for the {@link InstructionSearchPlugin}. This consists of two main panels - * for displaying instruction data, an area for control widgets, and a button panel: - * - * ------------------------------------ - * | | | - * | Instruction | Preview | - * | Panel | Panel | - * | | | - * |----------------------------------- - * | Control Widgets | - * ------------------------------------ - * | Button Panel | - * ------------------------------------ + * The GUI component for the {@link InstructionSearchPlugin}. */ - public class InstructionSearchDialog extends ReusableDialogComponentProvider implements Observer { private static final Color BG_COLOR_MARKERS = @@ -106,7 +92,7 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp try { revalidate(); - loadInstructions(plugin); + loadInstructions(); } catch (InvalidInputException e) { Msg.error(this, "Error loading instructions: " + e); @@ -121,32 +107,33 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp * Loads the currently-selected set of instructions in the listing and displays them in * the given dialog. * - * @param plugin the parent plugin * @throws InvalidInputException if there's a problem loading instructions */ - public void loadInstructions(InstructionSearchPlugin plugin) throws InvalidInputException { - loadInstructions(plugin.getProgramSelection(), plugin); + public void loadInstructions() throws InvalidInputException { + loadInstructions(plugin.getProgramSelection()); } /** * Loads the instructions in the given selection and displays them in the gui. * * @param selection the current selection - * @param plugin the parent plugin */ - public void loadInstructions(ProgramSelection selection, InstructionSearchPlugin plugin) { + public void loadInstructions(ProgramSelection selection) { MessagePanel msg = getMessagePanel(); if (selection == null && msg != null) { msg.setMessageText( "Select instructions from the listing (and hit reload) to populate the table.", Messages.NORMAL); + return; } - if (selection != null && plugin.isSelectionValid(selection, this)) { + if (plugin.isSelectionValid(selection, this)) { - if (getControlPanel() != null) { - getControlPanel().getRangeWidget().updateSearchRangeBySelection(); + ControlPanel panel = getControlPanel(); + if (panel != null) { + SelectionScopeWidget rangeWidget = panel.getRangeWidget(); + rangeWidget.updateSearchRangeBySelection(); } // Load the instructions, but note that we only allow a single selection range. If @@ -159,9 +146,8 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp * Adds the instructions in the given selection and displays them in the gui. * * @param selection the current selection - * @param plugin the parent plugin */ - public void addToInstructions(ProgramSelection selection, InstructionSearchPlugin plugin) { + public void addToInstructions(ProgramSelection selection) { MessagePanel msg = getMessagePanel(); if (selection == null && msg != null) { @@ -267,12 +253,12 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp plugin.firePluginEvent(new ProgramSelectionPluginEvent(plugin.getName(), selection, plugin.getCurrentProgram())); - SwingUtilities.invokeLater(() -> { + Swing.runLater(() -> { goToLocation(selection.getMinAddress()); try { - loadInstructions(selection, plugin); + loadInstructions(selection); } catch (Exception e) { Msg.error(this, "Error loading instructions", e); @@ -298,6 +284,13 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp getSearchData().clearAndReload(); } + void deleteInstruction(int index) { + InstructionSearchData data = getSearchData(); + InstructionTablePanel panel = getTablePanel(); + InstructionTable table = panel.getTable(); + data.deleteInstruction(table, index); + } + /** * Displays a message with the given text and color (severity). * @@ -317,26 +310,17 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp * UI to reflect the new instruction set, or simply update the preview panel in the case * where the user has simply changed the model by toggling masks. */ + @SuppressWarnings("deprecation") @Override public void update(Observable o, Object arg) { if (arg instanceof UpdateType) { UpdateType type = (UpdateType) arg; switch (type) { case RELOAD: - try { - revalidate(); - } - catch (InvalidInputException e) { - Msg.error(this, "Error loading instructions", e); - } + revalidate(); break; case UPDATE: - try { - tablePanel.buildPreview(); - } - catch (InvalidInputException e) { - Msg.error(this, "Error updating instructions", e); - } + tablePanel.buildPreview(); break; } } @@ -349,30 +333,18 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp /** * Updates the GUI when the user has made a new selection. For simplicity, this * removes the entire work panel and recreates it with the new instructions. - * - * @throws InvalidInputException */ - protected void revalidate() throws InvalidInputException { + protected void revalidate() { removeExistingGuiComponents(); createGuiComponents(); } - /** - * This dialog uses the 'apply' button as the search button, so override its behavior - * to initiate a search on activation. - */ @Override protected void applyCallback() { searchButtonActionPerformed(); } - /** - * Creates the main panel. - * - * @return the new panel - * @throws InvalidInputException if there's a problem constructing the panel - */ - protected JPanel createWorkPanel() throws InvalidInputException { + protected JPanel createWorkPanel() { // Create the main panel; use a border layout so all components // will adjust to fill the given space, allocating all leftover @@ -407,7 +379,7 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp * PRIVATE METHODS ********************************************************************************************/ - private void createGuiComponents() throws InvalidInputException { + private void createGuiComponents() { addWorkPanel(createWorkPanel()); createButtons(); @@ -492,33 +464,28 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp new TaskLauncher(searchTask, getFocusComponent()); } - /** - * Pops up a dialog containing the given search results. - * - * @param searchResults - */ public void displaySearchResults(List searchResults) { - Address[] tableArray = new Address[searchResults.size()]; - for (int x = 0; x < searchResults.size(); x++) { - tableArray[x] = searchResults.get(x).getAddr(); - } - TableService ts = plugin.getTool().getService(TableService.class); if (ts == null) { Msg.error(null, "Unable to show addresses, no table service available"); + return; } - else { - // The results window can be set to allow selection of multiple search results, - // provided the results are all the same size. This should be the case for us and - // as we're matching bytes, the size should always be divisible by 8. But do a check - // anyway. - int matchSize = 1; - if (searchData.getValueString().length() % 8 == 0) { - matchSize = searchData.getValueString().length() / 8; - } - show("Addresses", ts, tableArray, matchSize); + + Address[] tableArray = new Address[searchResults.size()]; + for (int i = 0; i < searchResults.size(); i++) { + tableArray[i] = searchResults.get(i).getAddr(); } + + // The results window can be set to allow selection of multiple search results, + // provided the results are all the same size. This should be the case for us and + // as we're matching bytes, the size should always be divisible by 8. But do a check + // anyway. + int matchSize = 1; + if (searchData.getValueString().length() % 8 == 0) { + matchSize = searchData.getValueString().length() / 8; + } + show("Addresses", ts, tableArray, matchSize); } /** @@ -529,20 +496,19 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp * @param addresses the list of addresses to display * @param matchSize the size of each match in the results table, in bytes */ - private void show(final String title, final TableService table, final Address[] addresses, - int matchSize) { - Runnable runnable = () -> { - AddressArrayTableModel model = new AddressArrayTableModel("Instruction Pattern Search", - plugin.getTool(), plugin.getCurrentProgram(), addresses); + private void show(String title, TableService table, Address[] addresses, int matchSize) { + + Swing.runLater(() -> { + + InstructionSearchTableModel model = + new InstructionSearchTableModel(addresses, matchSize); - model.setSelectionSize(matchSize); TableComponentProvider

tableProvider = table.showTableWithMarkers(title + " " + model.getName(), "Instruction Search Results", model, BG_COLOR_MARKERS, null, "Search", null); tableProvider.installRemoveItemsAction(); - }; - SystemUtilities.runSwingLater(runnable); + }); } private void goToLocation(Address addr) { @@ -553,4 +519,39 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp public InstructionSearchPlugin getPlugin() { return plugin; } + + private class InstructionSearchTableModel extends AddressArrayTableModel { + + private int matchSize; + + public InstructionSearchTableModel(Address[] addrs, int matchSize) { + super("Instruction Pattern Search", plugin.getTool(), plugin.getCurrentProgram(), + addrs); + this.matchSize = matchSize; + } + + @Override + public ProgramSelection getProgramSelection(int[] rows) { + if (matchSize == 1) { + return super.getProgramSelection(rows); + } + + int addOn = matchSize - 1; + AddressSet addressSet = new AddressSet(); + for (int element : rows) { + Address minAddr = getAddress(element); + Address maxAddr = minAddr; + try { + maxAddr = minAddr.addNoWrap(addOn); + addressSet.addRange(minAddr, maxAddr); + } + catch (AddressOverflowException e) { + Msg.debug(this, + "Unable to add address range for addresses: " + minAddr + ", " + maxAddr); + } + } + return new ProgramSelection(addressSet); + } + + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchMainPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchMainPanel.java index 01ac50ea60..46d1643c02 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchMainPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchMainPanel.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,12 +16,12 @@ package ghidra.app.plugin.core.instructionsearch.ui; import java.awt.BorderLayout; +import java.util.Objects; import javax.swing.*; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.InstructionSearchData; -import ghidra.util.exception.InvalidInputException; /** * Container for the {@link InstructionTable} and {@link PreviewTable}. @@ -53,10 +53,9 @@ public class InstructionSearchMainPanel extends JPanel { * * @param plugin the instruction search plugin * @param dialog the parent dialog - * @throws InvalidInputException if the search data is invalid */ public InstructionSearchMainPanel(final InstructionSearchPlugin plugin, - InstructionSearchDialog dialog) throws InvalidInputException { + InstructionSearchDialog dialog) { super(new BorderLayout()); @@ -64,9 +63,7 @@ public class InstructionSearchMainPanel extends JPanel { // Get the number of operands we need to show (across all instructions); this // will define the number of columns to create. - if (searchData == null) { - throw new InvalidInputException("Search data object cannot be null"); - } + Objects.requireNonNull(searchData); int operands = searchData.getMaxNumOperands(); // Create the two main panels and link their scroll panes so they'll always @@ -100,10 +97,8 @@ public class InstructionSearchMainPanel extends JPanel { * Displays the current search strings based on all user settings. What is * displayed in the {@link PreviewTablePanel} is what will be used for any * subsequent searches. - * - * @throws InvalidInputException */ - public void buildPreview() throws InvalidInputException { + public void buildPreview() { previewTablePanel.buildPreview(); } @@ -128,7 +123,7 @@ public class InstructionSearchMainPanel extends JPanel { * * @param panel1 left panel * @param panel2 right panel - * @return + * @return the split pane */ private JSplitPane splitPanels(JPanel panel1, JPanel panel2) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java index 32418ed56d..81cf926f68 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java @@ -27,12 +27,15 @@ import generic.theme.GThemeDefaults.Colors.Messages; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.*; import ghidra.app.services.GoToService; +import ghidra.app.util.HelpTopics; import ghidra.program.model.address.Address; import ghidra.program.model.lang.OperandType; import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.exception.InvalidInputException; import ghidra.util.task.*; +import help.Help; +import help.HelpService; import resources.Icons; import resources.ResourceManager; @@ -85,10 +88,47 @@ public class InstructionTable extends AbstractInstructionTable { // The data model will want to know when items in this table change...so have it register. // This will trigger the model to be updated as the user is toggling mask settings. dialog.getSearchData().registerForGuiUpdates(this); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + maybePopup(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + maybePopup(e); + } + + private void maybePopup(MouseEvent e) { + if (e.isPopupTrigger()) { + showPopup(e); + } + } + + private void showPopup(MouseEvent e) { + + int row = rowAtPoint(e.getPoint()); + if (row < 0) { + return; + } + + JPopupMenu popup = new JPopupMenu(); + JMenuItem menuCopy = new JMenuItem("Delete Instruction"); + menuCopy.addActionListener(event -> { + dialog.deleteInstruction(row); + }); + popup.add(menuCopy); + + popup.show(e.getComponent(), e.getX(), e.getY()); + } + + }); } @Override protected boolean supportsPopupActions() { + // this controls the built-in actions, such as copy return false; } @@ -128,26 +168,27 @@ public class InstructionTable extends AbstractInstructionTable { @Override protected JToolBar createToolbar() { - JToolBar toolbar1 = new JToolBar(); - toolbar1.add(Box.createHorizontalGlue()); + JToolBar newToolBar = new JToolBar(); + newToolBar.add(Box.createHorizontalGlue()); - createMaskClearAllBtn(toolbar1); - toolbar1.addSeparator(); - createMaskDataBtn(toolbar1); - createMaskOperandsBtn(toolbar1); - createMaskScalarsBtn(toolbar1); - createMaskAddressesBtn(toolbar1); - toolbar1.addSeparator(); - createReloadBtn(toolbar1); - createAddBtn(toolbar1); - toolbar1.addSeparator(); - createManualEditBtn(toolbar1); - toolbar1.addSeparator(); - createGoToAddressBtn(toolbar1); + createDeleteAllBtn(newToolBar); + createMaskClearAllBtn(newToolBar); + newToolBar.addSeparator(); + createMaskDataBtn(newToolBar); + createMaskOperandsBtn(newToolBar); + createMaskScalarsBtn(newToolBar); + createMaskAddressesBtn(newToolBar); + newToolBar.addSeparator(); + createReloadBtn(newToolBar); + createAddBtn(newToolBar); + newToolBar.addSeparator(); + createManualEditBtn(newToolBar); + newToolBar.addSeparator(); + createGoToAddressBtn(newToolBar); - toolbar1.setFloatable(false); + newToolBar.setFloatable(false); - return toolbar1; + return newToolBar; } /** @@ -287,6 +328,13 @@ public class InstructionTable extends AbstractInstructionTable { createToolbarButton(buttonToolbar, icon, action, "nav button"); } + private void createDeleteAllBtn(JToolBar buttonToolbar) { + Icon icon = Icons.DELETE_ICON; + Icon scaledIcon = ResourceManager.getScaledIcon(icon, ICON_SIZE, ICON_SIZE); + Action action = new DeleteAllAction("undefined", scaledIcon, "Delete all"); + createToolbarButton(buttonToolbar, icon, action, "delete all button"); + } + private void createMaskClearAllBtn(JToolBar buttonToolbar) { Icon icon = Icons.CLEAR_ICON; Icon scaledIcon = ResourceManager.getScaledIcon(icon, ICON_SIZE, ICON_SIZE); @@ -298,7 +346,8 @@ public class InstructionTable extends AbstractInstructionTable { Icon icon = Icons.REFRESH_ICON; Icon scaledIcon = ResourceManager.getScaledIcon(icon, ICON_SIZE, ICON_SIZE); Action action = - new ReloadAction("undefined", scaledIcon, "Load selected instructions from listing"); + new ReloadAction("undefined", scaledIcon, + "Reload using selected instructions from listing"); createToolbarButton(buttonToolbar, icon, action, "reload"); } @@ -306,8 +355,8 @@ public class InstructionTable extends AbstractInstructionTable { Icon icon = Icons.ADD_ICON; Icon scaledIcon = ResourceManager.getScaledIcon(icon, ICON_SIZE, ICON_SIZE); Action action = - new AddAction("undefined", scaledIcon, "Add selected instructions from listing"); - createToolbarButton(buttonToolbar, icon, action, "add"); + new AddAction("undefined", scaledIcon, "Append selected instructions from listing"); + createToolbarButton(buttonToolbar, icon, action, "append"); } private void createManualEditBtn(JToolBar buttonToolbar) { @@ -357,6 +406,11 @@ public class InstructionTable extends AbstractInstructionTable { button.setName(name); button.setHideActionText(true); toolbar1.add(button); + + //Instruction_Pattern_Actions + HelpLocation loc = new HelpLocation(HelpTopics.SEARCH, "Instruction_Pattern_Actions"); + HelpService help = Help.getHelpService(); + help.registerHelp(button, loc); } private void processInstruction(InstructionTableDataObject[][] dataObjects, @@ -388,6 +442,19 @@ public class InstructionTable extends AbstractInstructionTable { } + private class DeleteAllAction extends AbstractAction { + + public DeleteAllAction(String text, Icon icon, String desc) { + super(text, icon); + putValue(SHORT_DESCRIPTION, desc); + } + + @Override + public void actionPerformed(ActionEvent e) { + dialog.clear(); + } + } + private class ClearMasksAction extends AbstractAction { public ClearMasksAction(String text, Icon icon, String desc) { @@ -411,7 +478,7 @@ public class InstructionTable extends AbstractInstructionTable { @Override public void actionPerformed(ActionEvent e) { try { - dialog.loadInstructions(plugin); + dialog.loadInstructions(); } catch (InvalidInputException e1) { Msg.error(this, "Error loading instructions: " + e); @@ -428,7 +495,7 @@ public class InstructionTable extends AbstractInstructionTable { @Override public void actionPerformed(ActionEvent e) { - dialog.addToInstructions(plugin.getProgramSelection(), plugin); + dialog.addToInstructions(plugin.getProgramSelection()); } } @@ -442,7 +509,8 @@ public class InstructionTable extends AbstractInstructionTable { @Override public void actionPerformed(ActionEvent e) { insertBytesWidget = getInsertBytesWidget(); - plugin.getTool().showDialog(insertBytesWidget, plugin.getSearchDialog().getComponent()); + JComponent dialogComponent = dialog.getComponent(); + plugin.getTool().showDialog(insertBytesWidget, dialogComponent); } } @@ -512,12 +580,12 @@ public class InstructionTable extends AbstractInstructionTable { @Override public void actionPerformed(ActionEvent e) { - GoToService gs = plugin.getTool().getService(GoToService.class); // Only go somewhere if something is actually in the table. If it's empty this makes // no sense. Note that the plugin.getInstructions() call can never be null, so no // need to check that here. - if (dialog.getSearchData().getInstructions().size() <= 0) { + List instructions = searchData.getInstructions(); + if (instructions.size() <= 0) { return; } @@ -526,19 +594,25 @@ public class InstructionTable extends AbstractInstructionTable { // manually (hence no actual location in the listing). In this case, just search // for the first instance of this instruction and navigate there. If search returns // no results, display a message to the user. - Address firstAddr = dialog.getSearchData().getInstructions().get(0).getAddr(); - - if (firstAddr != null) { - gs.goTo(firstAddr); - } - else { - if (dialog.getMessagePanel() != null) { - dialog.getMessagePanel() - .setMessageText( - "Instruction was loaded manually, no address in the listing to navigate to.", - Messages.NORMAL); + Address firstAddr = instructions.get(0).getAddr(); + if (firstAddr == null) { + MessagePanel messagePanel = dialog.getMessagePanel(); + if (messagePanel != null) { + messagePanel.setMessageText( + "Instruction was loaded manually, no address in the listing to navigate to.", + Messages.NORMAL); } + return; } + + GoToService service = plugin.getTool().getService(GoToService.class); + if (service == null) { + Msg.showError(this, null, "Missing GoToSerivce", + "Cannot navigate without the GoToService installed"); + return; + } + + service.goTo(firstAddr); } } @@ -553,9 +627,11 @@ public class InstructionTable extends AbstractInstructionTable { */ private InstructionTableDataObject[][] processMnemonic(int row, int col, InstructionTableDataObject[][] dataObjects) { + + List instructions = searchData.getInstructions(); dataObjects[row][col] = new InstructionTableDataObject( - dialog.getSearchData().getInstructions().get(row).getTextRep(), - dialog.getSearchData().getInstructions().get(row).isInstruction(), + instructions.get(row).getTextRep(), + instructions.get(row).isInstruction(), OperandState.NOT_MASKED); return dataObjects; @@ -580,19 +656,18 @@ public class InstructionTable extends AbstractInstructionTable { // First get the operand information (if any exist) for this instruction. // Note, the getOperands() call will never return null so we're safe here. - List operands = - dialog.getSearchData().getInstructions().get(row).getOperands(); + List instructions = searchData.getInstructions(); + InstructionMetadata metadata = instructions.get(row); + List operands = metadata.getOperands(); if (operands.size() > col - 1) { - operandMetadata = - dialog.getSearchData().getInstructions().get(row).getOperands().get(col - 1); + operandMetadata = metadata.getOperands().get(col - 1); } // If here then we have a valid operand, so store it. if (operandMetadata != null) { InstructionTableDataObject obj = new InstructionTableDataObject(operandMetadata.getTextRep(), - dialog.getSearchData().getInstructions().get(row).isInstruction(), - OperandState.NOT_MASKED); + metadata.isInstruction(), OperandState.NOT_MASKED); obj.setOperandCase(operandMetadata); dataObjects[row][col] = obj; } @@ -602,7 +677,7 @@ public class InstructionTable extends AbstractInstructionTable { // NA will cause it to not be able to be toggled on/off by the user. else { dataObjects[row][col] = new InstructionTableDataObject("", - dialog.getSearchData().getInstructions().get(row).isInstruction(), OperandState.NA); + metadata.isInstruction(), OperandState.NA); } return dataObjects; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTableCellRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTableCellRenderer.java index 874ca09384..3b203e007b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTableCellRenderer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTableCellRenderer.java @@ -34,7 +34,7 @@ public class InstructionTableCellRenderer extends GhidraTableCellRenderer { private static final String FONT_ID = "font.plugin.instruction.table.renderer"; public InstructionTableCellRenderer() { - super(Gui.getFont(FONT_ID)); + setBaseFontId(FONT_ID); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/SearchInstructionsTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/SearchInstructionsTask.java index d3902ba782..40c5dfe8bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/SearchInstructionsTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/SearchInstructionsTask.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. @@ -20,12 +20,14 @@ import java.util.*; import generic.theme.GThemeDefaults.Colors.Messages; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.InstructionMetadata; +import ghidra.app.plugin.core.instructionsearch.model.InstructionSearchData; import ghidra.app.plugin.core.instructionsearch.ui.SearchDirectionWidget.Direction; import ghidra.app.services.GoToService; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.*; import ghidra.program.util.BytesFieldLocation; +import ghidra.program.util.ProgramLocation; import ghidra.util.Swing; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; @@ -58,10 +60,9 @@ class SearchInstructionsTask extends Task { } @Override - public void run(TaskMonitor taskMonitor) { - if (taskMonitor == null) { - return; - } + public void run(TaskMonitor monitor) { + + monitor = TaskMonitor.dummyIfNull(monitor); // First get all the search ranges we have to search. List searchRanges = @@ -107,16 +108,18 @@ class SearchInstructionsTask extends Task { } if (searchRanges.size() > 1) { - taskMonitor.setMessage( + monitor.setMessage( "Searching range " + rangeNum + " of " + searchRanges.size()); } else { - taskMonitor.setMessage("Searching..."); + monitor.setMessage("Searching..."); } // And SEARCH. + ProgramLocation location = searchPlugin.getProgramLocation(); + InstructionSearchData searchData = searchDialog.getSearchData(); InstructionMetadata searchResults = - searchDialog.getSearchData().search(searchPlugin, range, taskMonitor, forward); + searchData.search(location, range, forward, monitor); // If there are results, move the cursor there, otherwise keep looping and check // the next range. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressPreviewTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressPreviewTableModel.java index 99036c3fbb..7b39e37838 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressPreviewTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/AddressPreviewTableModel.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,10 +16,8 @@ package ghidra.util.table; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramSelection; -import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; /** @@ -31,8 +29,6 @@ import ghidra.util.task.TaskMonitor; */ public abstract class AddressPreviewTableModel extends AddressBasedTableModel
{ - private int selectionSize = 1; - /** * Constructor. * @@ -60,49 +56,8 @@ public abstract class AddressPreviewTableModel extends AddressBasedTableModel clazz = getClass(); String simpleName = clazz.getSimpleName(); @@ -339,11 +351,31 @@ public abstract class GhidraScreenShotGenerator extends AbstractScreenShotGenera writeFile(imageFile); } - protected File getHelpTopicDir(String helpTopic) { + protected File getHelpTopicDir(String topicName) { + return getHelpTopicDir(null, topicName); + } + + protected File getHelpTopicDir(String moduleName, String helpTopic) { List helpTopicDirs = getHelpTopicDirs(); for (File file : helpTopicDirs) { File potential = new File(file, helpTopic); - if (potential.exists()) { + if (!potential.exists()) { + continue; + } + + if (StringUtils.isBlank(moduleName)) { + return potential; // any module will do + } + + // Module/src/main/help/help/topics/TopicName + File topics = potential.getParentFile(); + File help = topics.getParentFile(); + File helpParent = help.getParentFile(); + File main = helpParent.getParentFile(); + File src = main.getParentFile(); + File module = src.getParentFile(); + String actualModuleName = module.getName(); + if (actualModuleName.equals(moduleName)) { return potential; } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchTest.java index 03c453441f..8d3d1956b0 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchTest.java @@ -228,7 +228,7 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest { // Now create a selection to add an instruction and call 'add' createSelection("0x004065e6", "0x004065e6"); - pressButtonByName(component, "add"); + pressButtonByName(component, "append"); waitForTasks(); // grab the rebuilt table @@ -237,6 +237,21 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest { assertInstructionValue(8, "PUSH EAX"); } + @Test + public void testDeleteAll() throws Exception { + + // sanity check + assertEquals(8, instructionTable.getRowCount()); + assertInstructionValue(0, "INC EDI"); + assertInstructionValue(7, "MOV dword ptr [EBP + -0x4] EAX"); + + pressButtonByName(component, "delete all button"); + waitForTasks(); + + instructionTable = dialog.getTablePanel().getTable(); + assertEquals(0, instructionTable.getRowCount()); + } + /** * Tests that the {@link PreviewTable} is properly loaded when instructions are selected. */ @@ -695,6 +710,7 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testManualInsert_Invalid() { + setErrorsExpected(true); loadBytes("0000 abcd efgh 01011"); instructionTable = dialog.getTablePanel().getTable(); @@ -706,6 +722,7 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest { Window errorDialog = waitForWindowByTitleContaining("Input Error"); assertNotNull(errorDialog); runSwing(() -> errorDialog.setVisible(false)); + setErrorsExpected(false); } /** diff --git a/Ghidra/Features/FileFormats/data/ExtensionPoint.manifest b/Ghidra/Features/FileFormats/data/ExtensionPoint.manifest index aebb112e35..9714ddd282 100644 --- a/Ghidra/Features/FileFormats/data/ExtensionPoint.manifest +++ b/Ghidra/Features/FileFormats/data/ExtensionPoint.manifest @@ -1,4 +1,3 @@ Decryptor -FileSystem FileSystemModel FSBFileHandler diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AbstractSearchScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AbstractSearchScreenShots.java index 64c94b879d..dce2cf4d74 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AbstractSearchScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/AbstractSearchScreenShots.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,6 +28,13 @@ import generic.theme.GThemeDefaults.Colors.Palette; protected static final Color NAVY = Palette.getColor("navy"); protected static final Color DARK_GREEN = Palette.getColor("darkgreen"); + @Override + protected String getHelpTopicModuleName() { + // Most Search help is in Base; some is in Docking. If the Docking help needs screenshots, + // then it will have to override this override. + return "Base"; + } + @Override protected String getHelpTopicName() { return "Search"; diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/InstructionPatternSearchScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/InstructionPatternSearchScreenShots.java index 8d8a913cb9..73e81be603 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/InstructionPatternSearchScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/InstructionPatternSearchScreenShots.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,6 +25,8 @@ import org.junit.Test; import docking.DialogComponentProvider; import docking.action.DockingActionIf; import generic.test.AbstractGuiTest; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.ui.*; import ghidra.app.plugin.core.table.TableComponentProvider; @@ -35,6 +37,7 @@ import ghidra.app.plugin.core.table.TableComponentProvider; public class InstructionPatternSearchScreenShots extends AbstractSearchScreenShots { private InstructionSearchPlugin instructionSearchPlugin; + private CodeViewerProvider codeViewer; @Override @Before @@ -44,6 +47,9 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho instructionSearchPlugin = env.getPlugin(InstructionSearchPlugin.class); + CodeBrowserPlugin cbp = env.getPlugin(CodeBrowserPlugin.class); + codeViewer = cbp.getProvider(); + env.showTool(); } @@ -52,7 +58,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); @@ -78,7 +84,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); captureDialog(DialogComponentProvider.class, 900, 461); @@ -96,7 +102,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); Component instrTable = @@ -117,7 +123,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); Component previewTable = @@ -137,7 +143,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); Component controlPanel = @@ -158,7 +164,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); Component instructionTable = @@ -180,7 +186,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); Component previewTable = @@ -205,7 +211,7 @@ public class InstructionPatternSearchScreenShots extends AbstractSearchScreenSho DockingActionIf openSearchDialogAction = getAction(instructionSearchPlugin, "Search Instruction Patterns"); - performAction(openSearchDialogAction, true); + performAction(openSearchDialogAction, codeViewer, true); waitForSwing(); DialogComponentProvider dialog = getDialog();