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
Expand Up @@ -48,9 +48,7 @@ private boolean shouldApplyMatch() {
return func != null &&
// Do not override user-defined function names
func.getSymbol().getSource() != SourceType.USER_DEFINED &&
// Exclude thunks and external functions
!func.isThunk() &&
!func.isExternal() &&
GhidraRevengService.isRelevantForAnalysis(func) &&
// Only accept valid names (no spaces)
!match.functionMatch().nearest_neighbor_mangled_function_name().contains(" ") &&
!match.functionMatch().nearest_neighbor_function_name().contains(" ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import ai.reveng.model.ConfigResponse;
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 ghidra.util.Msg;
import ghidra.util.Swing;
Expand All @@ -25,6 +27,7 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
private JCheckBox dynamicExecutionCheckBox;
private final Program program;
private final GhidraRevengService service;
private final PluginTool tool;
private JRadioButton privateScope;
private JRadioButton publicScope;
private JTextField tagsTextBox;
Expand All @@ -33,22 +36,25 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
private JCheckBox identifyCVECheckBox;
private JCheckBox generateSBOMCheckBox;
private JComboBox<String> architectureComboBox;
private FunctionSelectionPanel functionSelectionPanel;
private boolean okPressed = false;
private boolean configCheckPassed = false;

private JLabel fileSizeWarningLabel;
private JLabel loadingLabel;

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

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

buildInterface();
setPreferredSize(320, 420);
setPreferredSize(600, 550);

fetchConfigAsync();
}
Expand Down Expand Up @@ -176,18 +182,26 @@ private void buildInterface() {
loadingLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
workPanel.add(loadingLabel);

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

// Add function selection panel
functionSelectionPanel = new FunctionSelectionPanel(tool);
functionSelectionPanel.initForProgram(program);
functionSelectionPanel.getTableModel().addTableModelListener(e -> updateStartButtonState());
workPanel.add(functionSelectionPanel);


addCancelButton();
addOKButton();

okButton.setText("Start Analysis");
okButton.setEnabled(false); // Disabled until config check completes
okButton.setEnabled(false); // Disabled until config check completes and functions are selected
}

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 @@ -216,6 +230,10 @@ protected void okCallback() {
close();
}

public boolean isOkPressed() {
return okPressed;
}

@Override
public JComponent getComponent() {
return super.getComponent();
Expand All @@ -239,7 +257,8 @@ private void handleConfigResponse(@Nullable ConfigResponse config) {

if (config == null) {
// Config fetch failed, allow upload attempt (server will reject if too large)
okButton.setEnabled(true);
configCheckPassed = true;
updateStartButtonState();
return;
}

Expand All @@ -251,7 +270,8 @@ private void validateFileSize(long maxFileSizeBytes) {
long fileSize = getProgramFileSize();
if (fileSize < 0) {
// Could not determine file size, allow upload attempt
okButton.setEnabled(true);
configCheckPassed = true;
updateStartButtonState();
return;
}

Expand All @@ -262,13 +282,19 @@ private void validateFileSize(long maxFileSizeBytes) {
"<html><center>File size (%s) exceeds<br>server limit (%s)</center></html>"
.formatted(fileSizeStr, maxSizeStr));
fileSizeWarningLabel.setVisible(true);
okButton.setEnabled(false);
configCheckPassed = false;
updateStartButtonState();
} else {
fileSizeWarningLabel.setVisible(false);
okButton.setEnabled(true);
configCheckPassed = true;
updateStartButtonState();
}
}

private void updateStartButtonState() {
okButton.setEnabled(configCheckPassed && functionSelectionPanel.getSelectedCount() > 0);
}

private long getProgramFileSize() {
try {
Path filePath;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection;

import ai.reveng.toolkit.ghidra.core.services.api.types.FunctionInfo;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;

import javax.annotation.Nullable;

/**
* 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;
private boolean enabled = true;
@Nullable
private FunctionInfo remoteFunctionInfo;

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) {
if (!enabled) {
return;
}
this.selected = selected;
}

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (!enabled) {
this.selected = false;
}
}

@Nullable
public FunctionInfo getRemoteFunctionInfo() {
return remoteFunctionInfo;
}

public void setRemoteFunctionInfo(@Nullable FunctionInfo remoteFunctionInfo) {
this.remoteFunctionInfo = remoteFunctionInfo;
}

@Nullable
public String getRemoteFunctionName() {
return remoteFunctionInfo != null ? remoteFunctionInfo.functionName() : null;
}

@Nullable
public String getRemoteMangledName() {
return remoteFunctionInfo != null ? remoteFunctionInfo.functionMangledName() : null;
}

@Nullable
public Long getRemoteFunctionID() {
return remoteFunctionInfo != null ? remoteFunctionInfo.functionID().value() : null;
}
}
Loading
Loading