Skip to content

Commit 60b8553

Browse files
committed
Support filtering functions for analysis creation and attaching
# Conflicts: # src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/analysiscreation/RevEngAIAnalysisOptionsDialog.java # src/test/java/ai/reveng/AnalysisOptionsDialogTest.java
1 parent 95aa52a commit 60b8553

15 files changed

Lines changed: 1623 additions & 95 deletions

src/main/java/ai/reveng/toolkit/ghidra/Utils.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,43 @@ public static <ROW_TYPE, COLUMN_TYPE> void addRowToDescriptor(
5555
addRowToDescriptor(descriptor, columnName, true, columnTypeClass, rowObjectAccessor);
5656
}
5757

58+
/**
59+
* Helper method to add a column with sort ordinal specification.
60+
* @param sortOrdinal 1-based sort priority (1 = primary sort), or -1 for no default sort
61+
* @param ascending true for ascending sort, false for descending
62+
*/
63+
public static <ROW_TYPE, COLUMN_TYPE> void addRowToDescriptor(
64+
TableColumnDescriptor<ROW_TYPE> descriptor,
65+
String columnName,
66+
Class<COLUMN_TYPE> columnTypeClass,
67+
RowObjectAccessor<ROW_TYPE, COLUMN_TYPE> rowObjectAccessor,
68+
int sortOrdinal,
69+
boolean ascending) {
70+
71+
var column = new AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object>() {
72+
@Override
73+
public String getColumnName() {
74+
return columnName;
75+
}
76+
77+
@Override
78+
public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data, ServiceProvider serviceProvider) throws IllegalArgumentException {
79+
return rowObjectAccessor.access(rowObject);
80+
}
81+
82+
@Override
83+
public Class<COLUMN_TYPE> getColumnClass() {
84+
return columnTypeClass;
85+
}
86+
87+
@Override
88+
public Class<ROW_TYPE> getSupportedRowType() {
89+
return null;
90+
}
91+
};
92+
descriptor.addVisibleColumn(column, sortOrdinal, ascending);
93+
}
94+
5895

5996

6097
@FunctionalInterface

src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/cmds/ApplyMatchCmd.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ private boolean shouldApplyMatch() {
4848
return func != null &&
4949
// Do not override user-defined function names
5050
func.getSymbol().getSource() != SourceType.USER_DEFINED &&
51-
// Exclude thunks and external functions
52-
!func.isThunk() &&
53-
!func.isExternal() &&
51+
GhidraRevengService.isRelevantForAnalysis(func) &&
5452
// Only accept valid names (no spaces)
5553
!match.functionMatch().nearest_neighbor_mangled_function_name().contains(" ") &&
5654
!match.functionMatch().nearest_neighbor_function_name().contains(" ")

src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/analysiscreation/RevEngAIAnalysisOptionsDialog.java

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import ai.reveng.model.ConfigResponse;
44
import ai.reveng.toolkit.ghidra.binarysimilarity.ui.dialog.RevEngDialogComponentProvider;
5+
import ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection.FunctionSelectionPanel;
56
import ai.reveng.toolkit.ghidra.core.services.api.AnalysisOptionsBuilder;
67
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
78
import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisScope;
89
import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage;
10+
import ghidra.framework.plugintool.PluginTool;
911
import ghidra.program.model.listing.Program;
1012
import ghidra.util.Msg;
1113
import ghidra.util.Swing;
@@ -25,6 +27,7 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
2527
private JCheckBox dynamicExecutionCheckBox;
2628
private final Program program;
2729
private final GhidraRevengService service;
30+
private final PluginTool tool;
2831
private JRadioButton privateScope;
2932
private JRadioButton publicScope;
3033
private JTextField tagsTextBox;
@@ -33,22 +36,25 @@ public class RevEngAIAnalysisOptionsDialog extends RevEngDialogComponentProvider
3336
private JCheckBox identifyCVECheckBox;
3437
private JCheckBox generateSBOMCheckBox;
3538
private JComboBox<String> architectureComboBox;
39+
private FunctionSelectionPanel functionSelectionPanel;
3640
private boolean okPressed = false;
41+
private boolean configCheckPassed = false;
3742

3843
private JLabel fileSizeWarningLabel;
3944
private JLabel loadingLabel;
4045

41-
public static RevEngAIAnalysisOptionsDialog withModelsFromServer(Program program, GhidraRevengService reService) {
42-
return new RevEngAIAnalysisOptionsDialog(program, reService);
46+
public static RevEngAIAnalysisOptionsDialog withModelsFromServer(Program program, GhidraRevengService reService, PluginTool tool) {
47+
return new RevEngAIAnalysisOptionsDialog(program, tool, reService);
4348
}
4449

45-
public RevEngAIAnalysisOptionsDialog(Program program, GhidraRevengService service) {
50+
public RevEngAIAnalysisOptionsDialog(Program program, PluginTool tool, GhidraRevengService service) {
4651
super(ReaiPluginPackage.WINDOW_PREFIX + "Configure Analysis for %s".formatted(program.getName()), true);
4752
this.program = program;
4853
this.service = service;
54+
this.tool = tool;
4955

5056
buildInterface();
51-
setPreferredSize(320, 420);
57+
setPreferredSize(600, 550);
5258

5359
fetchConfigAsync();
5460
}
@@ -176,18 +182,26 @@ private void buildInterface() {
176182
loadingLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
177183
workPanel.add(loadingLabel);
178184

185+
workPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
186+
187+
// Add function selection panel
188+
functionSelectionPanel = new FunctionSelectionPanel(tool);
189+
functionSelectionPanel.initForProgram(program);
190+
functionSelectionPanel.getTableModel().addTableModelListener(e -> updateStartButtonState());
191+
workPanel.add(functionSelectionPanel);
192+
193+
179194
addCancelButton();
180195
addOKButton();
181196

182197
okButton.setText("Start Analysis");
183-
okButton.setEnabled(false); // Disabled until config check completes
198+
okButton.setEnabled(false); // Disabled until config check completes and functions are selected
184199
}
185200

186-
public @Nullable AnalysisOptionsBuilder getOptionsFromUI() {
187-
if (!okPressed) {
188-
return null;
189-
}
190-
var options = AnalysisOptionsBuilder.forProgram(program);
201+
public AnalysisOptionsBuilder getOptionsFromUI() {
202+
// Use the selected functions from the function selection panel
203+
var selectedFunctions = functionSelectionPanel.getSelectedFunctions();
204+
var options = AnalysisOptionsBuilder.forProgramWithFunctions(program, selectedFunctions);
191205

192206
options.skipScraping(!scrapeExternalTagsBox.isSelected());
193207
options.skipCapabilities(!identifyCapabilitiesCheckBox.isSelected());
@@ -216,6 +230,10 @@ protected void okCallback() {
216230
close();
217231
}
218232

233+
public boolean isOkPressed() {
234+
return okPressed;
235+
}
236+
219237
@Override
220238
public JComponent getComponent() {
221239
return super.getComponent();
@@ -239,7 +257,8 @@ private void handleConfigResponse(@Nullable ConfigResponse config) {
239257

240258
if (config == null) {
241259
// Config fetch failed, allow upload attempt (server will reject if too large)
242-
okButton.setEnabled(true);
260+
configCheckPassed = true;
261+
updateStartButtonState();
243262
return;
244263
}
245264

@@ -251,7 +270,8 @@ private void validateFileSize(long maxFileSizeBytes) {
251270
long fileSize = getProgramFileSize();
252271
if (fileSize < 0) {
253272
// Could not determine file size, allow upload attempt
254-
okButton.setEnabled(true);
273+
configCheckPassed = true;
274+
updateStartButtonState();
255275
return;
256276
}
257277

@@ -262,13 +282,19 @@ private void validateFileSize(long maxFileSizeBytes) {
262282
"<html><center>File size (%s) exceeds<br>server limit (%s)</center></html>"
263283
.formatted(fileSizeStr, maxSizeStr));
264284
fileSizeWarningLabel.setVisible(true);
265-
okButton.setEnabled(false);
285+
configCheckPassed = false;
286+
updateStartButtonState();
266287
} else {
267288
fileSizeWarningLabel.setVisible(false);
268-
okButton.setEnabled(true);
289+
configCheckPassed = true;
290+
updateStartButtonState();
269291
}
270292
}
271293

294+
private void updateStartButtonState() {
295+
okButton.setEnabled(configCheckPassed && functionSelectionPanel.getSelectedCount() > 0);
296+
}
297+
272298
private long getProgramFileSize() {
273299
try {
274300
Path filePath;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package ai.reveng.toolkit.ghidra.binarysimilarity.ui.functionselection;
2+
3+
import ai.reveng.toolkit.ghidra.core.services.api.types.FunctionInfo;
4+
import ghidra.program.model.address.Address;
5+
import ghidra.program.model.listing.Function;
6+
7+
import javax.annotation.Nullable;
8+
9+
/**
10+
* Wrapper around a Ghidra {@link Function} with a mutable selection flag.
11+
* Used to display functions in a table where users can select which functions
12+
* to include in analysis.
13+
*/
14+
public class FunctionRowObject {
15+
private final Function function;
16+
private boolean selected;
17+
private boolean enabled = true;
18+
@Nullable
19+
private FunctionInfo remoteFunctionInfo;
20+
21+
public FunctionRowObject(Function function, boolean selected) {
22+
this.function = function;
23+
this.selected = selected;
24+
}
25+
26+
public Function getFunction() {
27+
return function;
28+
}
29+
30+
public String getName() {
31+
return function.getName();
32+
}
33+
34+
public Address getAddress() {
35+
return function.getEntryPoint();
36+
}
37+
38+
/**
39+
* Returns the size of the function based on address count.
40+
*/
41+
public long getSize() {
42+
return function.getBody().getNumAddresses();
43+
}
44+
45+
public boolean isExternal() {
46+
return function.isExternal();
47+
}
48+
49+
public boolean isThunk() {
50+
return function.isThunk();
51+
}
52+
53+
/**
54+
* Returns a human-readable type string for the function.
55+
*/
56+
public String getType() {
57+
if (isExternal()) {
58+
return "External";
59+
} else if (isThunk()) {
60+
return "Thunk";
61+
} else {
62+
return "Normal";
63+
}
64+
}
65+
66+
public boolean isSelected() {
67+
return selected;
68+
}
69+
70+
public void setSelected(boolean selected) {
71+
if (!enabled) {
72+
return;
73+
}
74+
this.selected = selected;
75+
}
76+
77+
public boolean isEnabled() {
78+
return enabled;
79+
}
80+
81+
public void setEnabled(boolean enabled) {
82+
this.enabled = enabled;
83+
if (!enabled) {
84+
this.selected = false;
85+
}
86+
}
87+
88+
@Nullable
89+
public FunctionInfo getRemoteFunctionInfo() {
90+
return remoteFunctionInfo;
91+
}
92+
93+
public void setRemoteFunctionInfo(@Nullable FunctionInfo remoteFunctionInfo) {
94+
this.remoteFunctionInfo = remoteFunctionInfo;
95+
}
96+
97+
@Nullable
98+
public String getRemoteFunctionName() {
99+
return remoteFunctionInfo != null ? remoteFunctionInfo.functionName() : null;
100+
}
101+
102+
@Nullable
103+
public Long getRemoteFunctionID() {
104+
return remoteFunctionInfo != null ? remoteFunctionInfo.functionID().value() : null;
105+
}
106+
}

0 commit comments

Comments
 (0)