Skip to content

Commit a291e93

Browse files
committed
Add function table component to dev plugin
Helps with troubleshooting, i.e. which local functions are associated with a remote function, what the remote function ID is.
1 parent a702537 commit a291e93

2 files changed

Lines changed: 227 additions & 1 deletion

File tree

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package ai.reveng.toolkit.ghidra.devplugin;
2+
3+
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
4+
import ai.reveng.toolkit.ghidra.core.services.api.TypedApiInterface;
5+
import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage;
6+
import docking.widgets.table.TableColumnDescriptor;
7+
import docking.widgets.table.threaded.ThreadedTableModelStub;
8+
import ghidra.app.nav.Navigatable;
9+
import ghidra.app.nav.NavigatableRemovalListener;
10+
import ghidra.app.services.GoToService;
11+
import ghidra.framework.plugintool.ComponentProviderAdapter;
12+
import ghidra.framework.plugintool.PluginTool;
13+
import ghidra.program.model.address.Address;
14+
import ghidra.program.model.listing.Function;
15+
import ghidra.program.model.listing.Program;
16+
import ghidra.util.datastruct.Accumulator;
17+
import ghidra.util.table.GhidraFilterTable;
18+
import ghidra.util.task.TaskMonitor;
19+
20+
import javax.swing.*;
21+
import javax.swing.event.ListSelectionEvent;
22+
import javax.swing.event.ListSelectionListener;
23+
import java.awt.*;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.Optional;
27+
28+
import static ai.reveng.toolkit.ghidra.Utils.addRowToDescriptor;
29+
30+
/**
31+
* Component provider that displays all functions in the current program
32+
* along with their RevEng.AI metadata (function ID, remote name, mangled name).
33+
*/
34+
public class RevEngFunctionTableProvider extends ComponentProviderAdapter {
35+
36+
private final JPanel mainPanel;
37+
private final FunctionInfoTableModel tableModel;
38+
private final GhidraFilterTable<FunctionInfoRow> filterTable;
39+
private Program currentProgram;
40+
41+
public RevEngFunctionTableProvider(PluginTool tool, String owner) {
42+
super(tool, "RevEng.AI Function Table", owner);
43+
setIcon(ReaiPluginPackage.REVENG_16);
44+
45+
tableModel = new FunctionInfoTableModel(tool);
46+
filterTable = new GhidraFilterTable<>(tableModel);
47+
48+
// Navigate to function on row selection
49+
filterTable.getTable().getSelectionModel().addListSelectionListener(e -> {
50+
if (e.getValueIsAdjusting()) {
51+
return;
52+
}
53+
int row = filterTable.getTable().getSelectedRow();
54+
if (row < 0) {
55+
return;
56+
}
57+
FunctionInfoRow rowObject = tableModel.getRowObject(row);
58+
if (rowObject == null) {
59+
return;
60+
}
61+
GoToService goToService = tool.getService(GoToService.class);
62+
if (goToService != null) {
63+
goToService.goTo(rowObject.getAddress());
64+
}
65+
});
66+
67+
mainPanel = new JPanel(new BorderLayout());
68+
mainPanel.add(filterTable, BorderLayout.CENTER);
69+
}
70+
71+
@Override
72+
public JComponent getComponent() {
73+
return mainPanel;
74+
}
75+
76+
public void setProgram(Program program) {
77+
this.currentProgram = program;
78+
reload();
79+
}
80+
81+
public void reload() {
82+
tableModel.reload(currentProgram);
83+
}
84+
85+
/**
86+
* Row object holding a Ghidra function and its RevEng.AI metadata.
87+
*/
88+
static class FunctionInfoRow {
89+
private final Function function;
90+
private final TypedApiInterface.FunctionID remoteFunctionID;
91+
private final String remoteMangledName;
92+
93+
FunctionInfoRow(Function function, TypedApiInterface.FunctionID remoteFunctionID, String remoteMangledName) {
94+
this.function = function;
95+
this.remoteFunctionID = remoteFunctionID;
96+
this.remoteMangledName = remoteMangledName;
97+
}
98+
99+
public Function getFunction() {
100+
return function;
101+
}
102+
103+
public Address getAddress() {
104+
return function.getEntryPoint();
105+
}
106+
107+
public String getLocalName() {
108+
return function.getName();
109+
}
110+
111+
public long getSize() {
112+
return function.getBody().getNumAddresses();
113+
}
114+
115+
public boolean isThunk() {
116+
return function.isThunk();
117+
}
118+
119+
public Long getRemoteFunctionIDValue() {
120+
return remoteFunctionID != null ? remoteFunctionID.value() : null;
121+
}
122+
123+
public String getRemoteName() {
124+
// The Ghidra function name will have been updated to the remote name if available
125+
// but we track the original remote name separately via the mangled name
126+
return remoteFunctionID != null ? function.getName() : null;
127+
}
128+
129+
public String getRemoteMangledName() {
130+
return remoteMangledName;
131+
}
132+
133+
public boolean hasRemoteInfo() {
134+
return remoteFunctionID != null;
135+
}
136+
}
137+
138+
/**
139+
* Table model for displaying function info with RevEng.AI metadata.
140+
*/
141+
static class FunctionInfoTableModel extends ThreadedTableModelStub<FunctionInfoRow> {
142+
private final List<FunctionInfoRow> rows = new ArrayList<>();
143+
private final PluginTool pluginTool;
144+
145+
FunctionInfoTableModel(PluginTool tool) {
146+
super("RevEng.AI Function Info", tool);
147+
this.pluginTool = tool;
148+
}
149+
150+
void reload(Program program) {
151+
rows.clear();
152+
if (program == null) {
153+
reload();
154+
return;
155+
}
156+
157+
GhidraRevengService service = pluginTool.getService(GhidraRevengService.class);
158+
Optional<GhidraRevengService.AnalysedProgram> analysedOpt =
159+
service != null ? service.getAnalysedProgram(program) : Optional.empty();
160+
161+
program.getFunctionManager().getFunctions(true).forEach(function -> {
162+
TypedApiInterface.FunctionID fID = null;
163+
String mangledName = null;
164+
165+
if (analysedOpt.isPresent()) {
166+
var analysed = analysedOpt.get();
167+
var fWithID = analysed.getIDForFunction(function);
168+
if (fWithID.isPresent()) {
169+
fID = fWithID.get().functionID();
170+
try {
171+
mangledName = analysed.getMangledNameForFunction(function);
172+
} catch (Exception ignored) {
173+
// mangled name map may not exist yet
174+
}
175+
}
176+
}
177+
178+
rows.add(new FunctionInfoRow(function, fID, mangledName));
179+
});
180+
reload();
181+
}
182+
183+
@Override
184+
protected void doLoad(Accumulator<FunctionInfoRow> accumulator, TaskMonitor monitor) {
185+
monitor.setMessage("Loading function info");
186+
monitor.setMaximum(rows.size());
187+
for (int i = 0; i < rows.size(); i++) {
188+
if (monitor.isCancelled()) {
189+
break;
190+
}
191+
accumulator.add(rows.get(i));
192+
monitor.setProgress(i + 1);
193+
}
194+
}
195+
196+
@Override
197+
protected TableColumnDescriptor<FunctionInfoRow> createTableColumnDescriptor() {
198+
TableColumnDescriptor<FunctionInfoRow> descriptor = new TableColumnDescriptor<>();
199+
addRowToDescriptor(descriptor, "Address", Address.class, FunctionInfoRow::getAddress, 1, true);
200+
addRowToDescriptor(descriptor, "Local Name", String.class, FunctionInfoRow::getLocalName);
201+
addRowToDescriptor(descriptor, "Size", Long.class, FunctionInfoRow::getSize);
202+
addRowToDescriptor(descriptor, "Thunk", Boolean.class, FunctionInfoRow::isThunk);
203+
addRowToDescriptor(descriptor, "Has Remote Info", Boolean.class, FunctionInfoRow::hasRemoteInfo);
204+
addRowToDescriptor(descriptor, "Remote Function ID", Long.class, FunctionInfoRow::getRemoteFunctionIDValue);
205+
addRowToDescriptor(descriptor, "Remote Mangled Name", String.class, FunctionInfoRow::getRemoteMangledName);
206+
return descriptor;
207+
}
208+
}
209+
}

src/main/java/ai/reveng/toolkit/ghidra/plugins/RevEngAIDevelopmentPlugin.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package ai.reveng.toolkit.ghidra.plugins;
22

3+
import ai.reveng.toolkit.ghidra.core.RevEngAIAnalysisResultsLoaded;
34
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
45
import ai.reveng.toolkit.ghidra.core.services.api.types.exceptions.APIConflictException;
6+
import ai.reveng.toolkit.ghidra.devplugin.RevEngFunctionTableProvider;
57
import ai.reveng.toolkit.ghidra.devplugin.RevEngMetadataProvider;
68
import docking.action.builder.ActionBuilder;
79
import docking.options.OptionsService;
810
import ghidra.app.DeveloperPluginPackage;
911
import ghidra.app.plugin.PluginCategoryNames;
1012
import ghidra.app.plugin.ProgramPlugin;
13+
import ghidra.framework.plugintool.PluginEvent;
1114
import ghidra.framework.plugintool.PluginInfo;
1215
import ghidra.framework.plugintool.PluginTool;
1316
import ghidra.framework.plugintool.util.PluginStatus;
@@ -32,19 +35,24 @@
3235
shortDescription = "Helper and Debug Tools for the RevEng.AI Toolkit",
3336
description = "Collection of tools that are not relevant for end user use," +
3437
"but are useful for developing your own scripts or debugging the RevEng.AI Toolkit",
35-
servicesRequired = { OptionsService.class, GhidraRevengService.class }
38+
servicesRequired = { OptionsService.class, GhidraRevengService.class },
39+
eventsConsumed = { RevEngAIAnalysisResultsLoaded.class }
3640
)
3741
//@formatter:on
3842
public class RevEngAIDevelopmentPlugin extends ProgramPlugin {
3943

4044
private final RevEngMetadataProvider revEngMetadataProvider;
45+
private final RevEngFunctionTableProvider functionTableProvider;
4146
private GhidraRevengService apiService;
4247

4348
public RevEngAIDevelopmentPlugin(PluginTool tool) {
4449
super(tool);
4550
revEngMetadataProvider = new RevEngMetadataProvider(tool, ReaiPluginPackage.NAME);
4651
tool.addComponentProvider(revEngMetadataProvider, false);
4752

53+
functionTableProvider = new RevEngFunctionTableProvider(tool, ReaiPluginPackage.NAME);
54+
tool.addComponentProvider(functionTableProvider, false);
55+
4856
var generateSignaturesAction = new ActionBuilder("Generate Signatures", ReaiPluginPackage.NAME)
4957
.menuPath(DEV_TOOLING_MENU_GROUP_NAME, "Generate Signatures for current program")
5058
.onAction(e -> {
@@ -82,6 +90,7 @@ public void run(TaskMonitor monitor) {
8290
@Override
8391
protected void programActivated(Program program) {
8492
revEngMetadataProvider.setProgram(program);
93+
functionTableProvider.setProgram(program);
8594
}
8695

8796
@Override
@@ -90,6 +99,14 @@ protected void locationChanged(ProgramLocation loc) {
9099
revEngMetadataProvider.locationChanged(loc);
91100
}
92101

102+
@Override
103+
public void processEvent(PluginEvent event) {
104+
super.processEvent(event);
105+
if (event instanceof RevEngAIAnalysisResultsLoaded) {
106+
functionTableProvider.reload();
107+
}
108+
}
109+
93110
@Override
94111
public void init() {
95112
super.init();

0 commit comments

Comments
 (0)