Merge remote-tracking branch 'origin/GP-2418-dragonmacher-instruction-search-add-button--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-10-28 10:51:21 -04:00
commit 9e589a451a
12 changed files with 208 additions and 71 deletions

View File

@ -46,36 +46,39 @@
<H4>Instruction Table Toolbar</H4>
<BLOCKQUOTE>
<P><IMG src="images/SearchInstructionPatternsInstructionTableToolbar.png" border="0" alt=
""> &nbsp;</P>
<P><IMG src="images/SearchInstructionPatternsInstructionTableToolbar.png" border="1" alt=
""></P>
<P>These tools provide ways to manipulate the Instruction Table and are discussed in
detail below:</P>
<BLOCKQUOTE>
<UL>
<LI>[ <IMG border="0" src="images/edit-clear.png"> ] Clears
<LI>[ <IMG border="0" src="Icons.CLEAR_ICON"> ] Clears
all masks.</LI>
<LI>[ <IMG border="0" src="images/DOSA_D.png"> ] Masks all
<LI>[ <IMG border="0" src="icon.plugin.instructiontable.undefined"> ] Masks all
data (non-instructions).</LI>
<LI>[ <IMG border="0" src="images/DOSA_O.png"> ] Masks all
<LI>[ <IMG border="0" src="icon.plugin.instructiontable.operand"> ] Masks all
operands.</LI>
<LI>[ <IMG border="0" src="images/DOSA_S.png"> ] Masks all
<LI>[ <IMG border="0" src="icon.plugin.instructiontable.scalar"> ] Masks all
scalar operands.</LI>
<LI>[ <IMG border="0" src="images/DOSA_A.png"> ] Masks all
<LI>[ <IMG border="0" src="icon.plugin.instructiontable.address"> ] Masks all
address operands.</LI>
<LI>[ <IMG border="0" src="images/reload.png"> ] Reloads the
<LI>[ <IMG border="0" src="Icons.REFRESH_ICON"> ] Reloads the
table from what is currently selected in the listing.</LI>
<LI>[ <IMG border="0" src="images/editbytes.gif"> ] Allows
<LI>[ <IMG border="0" src="Icons.ADD_ICON"> ] Add to the table what is currently
selected in the listing.</LI>
<LI>[ <IMG border="0" src="icon.plugin.instructiontable.manual.entry"> ] Allows
users to manually enter bytes to be loaded.</LI>
<LI>[ <IMG border="0" src="images/go-home.png"> ] Navigates in
<LI>[ <IMG border="0" src="Icons.HOME_ICON"> ] Navigates in
the listing to the location defined by this set of instructions.</LI>
</UL>
</BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -144,7 +144,7 @@ public class InstructionSearchPlugin extends ProgramPlugin {
// immediately return and display an error message if they do.
if (selection.getNumAddresses() == 0) {
dialog.displayMessage(
"Select instructions from the listing (and hit reload) to populate the table.",
"Select instructions from the listing (and hit reload/add) to populate the table.",
Messages.NORMAL);
return false;
}

View File

@ -30,9 +30,9 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.*;
/**
* This is the data model that {@link InstructionSearchDialog} instances use
@ -115,25 +115,72 @@ public class InstructionSearchData extends Observable {
throw new InvalidInputException("No instructions found in selection.");
}
TaskLauncher.launchModal("Loading Instructions", monitor -> {
TaskLauncher.launch(new LoadInstructionsTask(program, addrSet));
modelChanged(UpdateType.RELOAD);
}
/**
* Examines the given addresses from the given program to extract all instructions; results are
* stored in the local {@link InstructionMetadata} list.
*
* @param program the current program
* @param addresses the addresses to load instructions for
* @throws InvalidInputException if there's an error parsing the instructions
*/
public void add(Program program, AddressSetView addresses) throws InvalidInputException {
// 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 || addresses == null || addresses.isEmpty()) {
return;
}
// Do a quick check to see if we have any valid code units in the selection. If not,
// display an error message.
Listing listing = program.getListing();
CodeUnitIterator cuIter = listing.getCodeUnits(addresses, true);
if (!cuIter.hasNext()) {
throw new InvalidInputException("No instructions found in selection.");
}
TaskLauncher.launch(new LoadInstructionsTask(program, addresses));
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 {
SleighDebugLogger logger;
monitor.setIndeterminate(true);
while (cuIter.hasNext()) {
Listing listing = program.getListing();
CodeUnitIterator it = listing.getCodeUnits(addresses, true);
while (it.hasNext()) {
if (monitor.isCancelled()) {
return;
}
CodeUnit cu = cuIter.next();
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.
// mask info. If not, we don't need to create anything complex for masking - it's
// either on or off.
if (cu instanceof Instruction) {
logger =
SleighDebugLogger logger =
new SleighDebugLogger(program, cu.getAddress(), SleighDebugMode.VERBOSE);
if (logger.parseFailed()) {
Msg.showError(this, null, "Parsing error",
@ -162,9 +209,7 @@ public class InstructionSearchData extends Observable {
}
}
}
});
modelChanged(UpdateType.RELOAD);
}
}
/**
@ -349,8 +394,11 @@ public class InstructionSearchData extends Observable {
return;
}
instructions.get(row).getOperands().get(col).setMasked(
table.getCellData(row, col + 1).getState().equals(OperandState.MASKED));
instructions.get(row)
.getOperands()
.get(col)
.setMasked(
table.getCellData(row, col + 1).getState().equals(OperandState.MASKED));
}
/**
@ -674,10 +722,12 @@ public class InstructionSearchData extends Observable {
// 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) {
if (searchBounds.getMinAddress()
.compareTo(
plugin.getCurrentProgram().getMinAddress()) < 0 ||
searchBounds.getMaxAddress()
.compareTo(
plugin.getCurrentProgram().getMaxAddress()) > 0) {
throw new IllegalArgumentException(
"Search bounds are not valid; must be within the bounds of the program.");
}
@ -734,8 +784,11 @@ public class InstructionSearchData extends Observable {
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);
currentPosition = plugin.getCurrentProgram()
.getMemory()
.findBytes(currentPosition,
endAddress, maskContainer.getValue(), maskContainer.getMask(), true,
taskMonitor);
// If no match was found, currentPosition will be null.
if (currentPosition == null) {
@ -792,8 +845,11 @@ public class InstructionSearchData extends Observable {
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);
currentPosition = plugin.getCurrentProgram()
.getMemory()
.findBytes(currentPosition,
endAddress, maskContainer.getValue(), maskContainer.getMask(), false,
taskMonitor);
// If no match was found, currentPosition will be null.
if (currentPosition == null) {

View File

@ -52,7 +52,6 @@ public class InstructionTableDataObject {
// Some cell attributes.
private Color backgroundColor;
private Color foregroundColor = Colors.FOREGROUND;
private int fontStyle;
// The border style of the cell. This is used to facilitate the 3D look of the cells
// (bevel-styling).
@ -172,10 +171,6 @@ public class InstructionTableDataObject {
return foregroundColor;
}
public int getFontStyle() {
return fontStyle;
}
public OperandState getState() {
return state;
}

View File

@ -133,13 +133,12 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp
*
* @param selection the current selection
* @param plugin the parent plugin
* @throws InvalidInputException if there's a problem loading instructions
*/
public void loadInstructions(ProgramSelection selection, InstructionSearchPlugin plugin)
throws InvalidInputException {
public void loadInstructions(ProgramSelection selection, InstructionSearchPlugin plugin) {
if (selection == null && getMessagePanel() != null) {
getMessagePanel().setMessageText(
MessagePanel msg = getMessagePanel();
if (selection == null && msg != null) {
msg.setMessageText(
"Select instructions from the listing (and hit reload) to populate the table.",
Messages.NORMAL);
}
@ -156,6 +155,26 @@ 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) {
MessagePanel msg = getMessagePanel();
if (selection == null && msg != null) {
msg.setMessageText(
"Select instructions from the listing (and hit add) to update the table.",
Messages.NORMAL);
}
if (selection != null && plugin.isSelectionValid(selection, this)) {
addToSearchData(plugin.getCurrentProgram(), selection);
}
}
/**
* Loads instructions at the given program/selection and populates the search
* data object.
@ -177,6 +196,20 @@ public class InstructionSearchDialog extends ReusableDialogComponentProvider imp
}
}
private void addToSearchData(Program currentProgram, ProgramSelection selection) {
if (selection == null || currentProgram == null) {
return;
}
try {
getSearchData().add(currentProgram, selection);
}
catch (InvalidInputException e) {
Msg.error(this, "Error adding to search data", e);
}
}
public ControlPanel getControlPanel() {
return controlPanel;
}

View File

@ -139,6 +139,7 @@ public class InstructionTable extends AbstractInstructionTable {
createMaskAddressesBtn(toolbar1);
toolbar1.addSeparator();
createReloadBtn(toolbar1);
createAddBtn(toolbar1);
toolbar1.addSeparator();
createManualEditBtn(toolbar1);
toolbar1.addSeparator();
@ -301,6 +302,14 @@ public class InstructionTable extends AbstractInstructionTable {
createToolbarButton(buttonToolbar, icon, action, "reload");
}
private void createAddBtn(JToolBar buttonToolbar) {
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");
}
private void createManualEditBtn(JToolBar buttonToolbar) {
Icon icon = new GIcon("icon.plugin.instructiontable.manual.entry");
Icon scaledIcon = ResourceManager.getScaledIcon(icon, ICON_SIZE, ICON_SIZE);
@ -410,6 +419,19 @@ public class InstructionTable extends AbstractInstructionTable {
}
}
private class AddAction extends AbstractAction {
public AddAction(String text, Icon icon, String desc) {
super(text, icon);
putValue(SHORT_DESCRIPTION, desc);
}
@Override
public void actionPerformed(ActionEvent e) {
dialog.addToInstructions(plugin.getProgramSelection(), plugin);
}
}
private class ManualEntryAction extends AbstractAction {
public ManualEntryAction(String text, Icon icon, String desc) {

View File

@ -15,7 +15,8 @@
*/
package ghidra.app.plugin.core.instructionsearch.ui;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
@ -73,8 +74,6 @@ public class InstructionTableCellRenderer extends GhidraTableCellRenderer {
private void setForegroundAttributes(InstructionTableDataObject dataObject,
JLabel theRenderer) {
theRenderer.setForeground(dataObject.getForegroundColor());
Font newFont = theRenderer.getFont().deriveFont(dataObject.getFontStyle());
theRenderer.setFont(newFont);
}
private void setBackgroundAttributes(boolean isSelected, boolean hasFocus,

View File

@ -82,7 +82,10 @@ public class SelectionScopeWidget extends ControlPanelWidget {
searchRanges.clear();
AddressRangeIterator iterator =
plugin.getCurrentProgram().getMemory().getLoadedAndInitializedAddressSet().getAddressRanges();
plugin.getCurrentProgram()
.getMemory()
.getLoadedAndInitializedAddressSet()
.getAddressRanges();
while (iterator.hasNext()) {
searchRanges.add(iterator.next());
}
@ -175,14 +178,6 @@ public class SelectionScopeWidget extends ControlPanelWidget {
}
}
/**
* Creates a radio button with the given attributes.
*
* @param action
* @param name
* @param tooltip
* @return
*/
private JRadioButton createSearchRB(AbstractAction action, String name, String tooltip) {
GRadioButton button = new GRadioButton(action);
button.setName(name);

View File

@ -215,6 +215,28 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("EAX", obj72.getData());
}
@Test
public void testAddInstructions() throws Exception {
// start selection
// loadSelection("0x004065e1", "0x004065f2");
// sanity check
assertEquals(8, instructionTable.getRowCount());
assertInstructionValue(0, "INC EDI");
assertInstructionValue(7, "MOV dword ptr [EBP + -0x4] EAX");
// Now create a selection to add an instruction and call 'add'
createSelection("0x004065e6", "0x004065e6");
pressButtonByName(component, "add");
waitForTasks();
// grab the rebuilt table
instructionTable = dialog.getTablePanel().getTable();
assertEquals(9, instructionTable.getRowCount());
assertInstructionValue(8, "PUSH EAX");
}
/**
* Tests that the {@link PreviewTable} is properly loaded when instructions are selected.
*/
@ -768,6 +790,18 @@ public class InstructionSearchTest extends AbstractGhidraHeadedIntegrationTest {
* PRIVATE METHODS
********************************************************************************************/
private void assertInstructionValue(int row, String expectedText) {
InstructionTableDataObject cell1 =
(InstructionTableDataObject) instructionTable.getModel().getValueAt(row, 0);
InstructionTableDataObject cell2 =
(InstructionTableDataObject) instructionTable.getModel().getValueAt(row, 1);
InstructionTableDataObject cell3 =
(InstructionTableDataObject) instructionTable.getModel().getValueAt(row, 2);
String actualText = cell1.getData() + " " + cell2.getData() + " " + cell3.getData();
assertEquals("Instruction value in table was not as expected", expectedText,
actualText.trim());
}
private void closeDialog() {
runSwing(() -> dialog.close());
waitForSwing();