Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/main/java/ai/reveng/toolkit/ghidra/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,43 @@ public static <ROW_TYPE, COLUMN_TYPE> void addRowToDescriptor(
addRowToDescriptor(descriptor, columnName, true, columnTypeClass, rowObjectAccessor);
}

/**
* Helper method to add a column with sort ordinal specification.
* @param sortOrdinal 1-based sort priority (1 = primary sort), or -1 for no default sort
* @param ascending true for ascending sort, false for descending
*/
public static <ROW_TYPE, COLUMN_TYPE> void addRowToDescriptor(
TableColumnDescriptor<ROW_TYPE> descriptor,
String columnName,
Class<COLUMN_TYPE> columnTypeClass,
RowObjectAccessor<ROW_TYPE, COLUMN_TYPE> rowObjectAccessor,
int sortOrdinal,
boolean ascending) {

var column = new AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object>() {
@Override
public String getColumnName() {
return columnName;
}

@Override
public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data, ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObjectAccessor.access(rowObject);
}

@Override
public Class<COLUMN_TYPE> getColumnClass() {
return columnTypeClass;
}

@Override
public Class<ROW_TYPE> getSupportedRowType() {
return null;
}
};
descriptor.addVisibleColumn(column, sortOrdinal, ascending);
}



@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package ai.reveng.toolkit.ghidra.binarysimilarity.ui.analysiscreation;

import ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog.RevEngDialogComponentProvider;
import ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection.FunctionSelectionPanel;
import ai.reveng.toolkit.ghidra.core.services.api.AnalysisOptionsBuilder;
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisScope;
import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;

import javax.annotation.Nullable;
Expand All @@ -16,6 +18,7 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
private JCheckBox advancedAnalysisCheckBox;
private JCheckBox dynamicExecutionCheckBox;
private final Program program;
private final PluginTool tool;
private JRadioButton privateScope;
private JRadioButton publicScope;
private JTextField tagsTextBox;
Expand All @@ -24,18 +27,20 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
private JCheckBox identifyCVECheckBox;
private JCheckBox generateSBOMCheckBox;
private JComboBox<String> architectureComboBox;
private FunctionSelectionPanel functionSelectionPanel;
private boolean okPressed = false;

public static RevEngAIAnalysisOptionsDialog withModelsFromServer(Program program, GhidraRevengService reService) {
return new RevEngAIAnalysisOptionsDialog(program);
public static RevEngAIAnalysisOptionsDialog withModelsFromServer(Program program, GhidraRevengService reService, PluginTool tool) {
return new RevEngAIAnalysisOptionsDialog(program, tool);
}

public RevEngAIAnalysisOptionsDialog(Program program) {
public RevEngAIAnalysisOptionsDialog(Program program, PluginTool tool) {
super(ReaiPluginPackage.WINDOW_PREFIX + "Configure Analysis for %s".formatted(program.getName()), true);
this.program = program;
this.tool = tool;

buildInterface();
setPreferredSize(320, 380);
setPreferredSize(600, 550);
}

private void buildInterface() {
Expand Down Expand Up @@ -144,17 +149,23 @@ private void buildInterface() {
workPanel.add(tagsLabel);
workPanel.add(tagsTextBox);

workPanel.add(new JSeparator(SwingConstants.HORIZONTAL));

// Add function selection panel
functionSelectionPanel = new FunctionSelectionPanel(tool);
functionSelectionPanel.initForProgram(program);
workPanel.add(functionSelectionPanel);

addCancelButton();
addOKButton();

okButton.setText("Start Analysis");
}

public @Nullable AnalysisOptionsBuilder getOptionsFromUI() {
if (!okPressed) {
return null;
}
var options = AnalysisOptionsBuilder.forProgram(program);
public AnalysisOptionsBuilder getOptionsFromUI() {
// Use the selected functions from the function selection panel
var selectedFunctions = functionSelectionPanel.getSelectedFunctions();
var options = AnalysisOptionsBuilder.forProgramWithFunctions(program, selectedFunctions);

options.skipScraping(!scrapeExternalTagsBox.isSelected());
options.skipCapabilities(!identifyCapabilitiesCheckBox.isSelected());
Expand Down Expand Up @@ -183,6 +194,10 @@ protected void okCallback() {
close();
}

public boolean isOkPressed() {
return okPressed;
}

@Override
public JComponent getComponent() {
return super.getComponent();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection;

import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;

/**
* Wrapper around a Ghidra {@link Function} with a mutable selection flag.
* Used to display functions in a table where users can select which functions
* to include in analysis.
*/
public class FunctionRowObject {
private final Function function;
private boolean selected;

public FunctionRowObject(Function function, boolean selected) {
this.function = function;
this.selected = selected;
}

public Function getFunction() {
return function;
}

public String getName() {
return function.getName();
}

public Address getAddress() {
return function.getEntryPoint();
}

/**
* Returns the size of the function based on address count.
*/
public long getSize() {
return function.getBody().getNumAddresses();
}

public boolean isExternal() {
return function.isExternal();
}

public boolean isThunk() {
return function.isThunk();
}

/**
* Returns a human-readable type string for the function.
*/
public String getType() {
if (isExternal()) {
return "External";
} else if (isThunk()) {
return "Thunk";
} else {
return "Normal";
}
}

public boolean isSelected() {
return selected;
}

public void setSelected(boolean selected) {
this.selected = selected;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection;

import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.table.GhidraFilterTable;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import java.awt.*;
import java.util.List;

/**
* A reusable panel for selecting functions from a Ghidra program.
* Contains a filterable table of functions with selection checkboxes,
* toolbar buttons for bulk selection operations, and a summary label.
*/
public class FunctionSelectionPanel extends JPanel {
private final FunctionSelectionTableModel tableModel;
private final GhidraFilterTable<FunctionRowObject> filterTable;
private final JLabel summaryLabel;

public FunctionSelectionPanel(ServiceProvider serviceProvider) {
super(new BorderLayout());

tableModel = new FunctionSelectionTableModel(serviceProvider);
filterTable = new GhidraFilterTable<>(tableModel);
summaryLabel = new JLabel();

buildInterface();

// Listen for table changes to update the summary
tableModel.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
updateSummaryLabel();
}
});
}

private void buildInterface() {
setBorder(new TitledBorder("Function Selection"));

// Toolbar with bulk selection buttons
JPanel toolbarPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));

JButton selectAllButton = new JButton("Select All");
selectAllButton.setName("selectAllButton");
selectAllButton.addActionListener(e -> {
tableModel.selectAll();
updateSummaryLabel();
});

JButton deselectAllButton = new JButton("Deselect All");
deselectAllButton.setName("deselectAllButton");
deselectAllButton.addActionListener(e -> {
tableModel.deselectAll();
updateSummaryLabel();
});

JButton excludeUserDefinedButton = new JButton("Exclude User-Defined");
excludeUserDefinedButton.setName("excludeUserDefinedButton");
excludeUserDefinedButton.setToolTipText("Deselect functions with user-defined name or signature");
excludeUserDefinedButton.addActionListener(e -> {
tableModel.deselectUserDefined();
updateSummaryLabel();
});

toolbarPanel.add(selectAllButton);
toolbarPanel.add(deselectAllButton);
toolbarPanel.add(excludeUserDefinedButton);

// Summary label on the right side of the toolbar
JPanel summaryPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
summaryPanel.add(summaryLabel);

JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(toolbarPanel, BorderLayout.WEST);
topPanel.add(summaryPanel, BorderLayout.EAST);

add(topPanel, BorderLayout.NORTH);
add(filterTable, BorderLayout.CENTER);

updateSummaryLabel();
}

/**
* Initialize the panel with functions from the given program.
* External functions are excluded. By default, all functions are selected.
*/
public void initForProgram(Program program) {
tableModel.initForProgram(program);
updateSummaryLabel();
}

/**
* Returns the list of currently selected functions.
*/
public List<Function> getSelectedFunctions() {
return tableModel.getSelectedFunctions();
}

/**
* Returns the count of selected functions.
*/
public int getSelectedCount() {
return tableModel.getSelectedCount();
}

/**
* Returns the total number of functions.
*/
public int getTotalFunctionCount() {
return tableModel.getTotalCount();
}

/**
* Returns the underlying table model.
*/
public FunctionSelectionTableModel getTableModel() {
return tableModel;
}

/**
* Returns the underlying filter table component.
*/
public GhidraFilterTable<FunctionRowObject> getFilterTable() {
return filterTable;
}

private void updateSummaryLabel() {
int selected = tableModel.getSelectedCount();
int total = tableModel.getTotalCount();
summaryLabel.setText(String.format("%d of %d functions selected", selected, total));
}
}
Loading
Loading