Skip to content

Commit b7ae94a

Browse files
committed
Refactor import, add more tests
1 parent 0f34c32 commit b7ae94a

File tree

3 files changed

+837
-42
lines changed

3 files changed

+837
-42
lines changed

src/main/java/reva/plugin/RevaProgramManager.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import ghidra.app.util.task.ProgramOpener;
2626
import ghidra.framework.main.AppInfo;
2727
import ghidra.framework.model.DomainFile;
28+
import ghidra.framework.model.DomainFolder;
2829
import ghidra.framework.model.Project;
2930
import ghidra.framework.model.ToolManager;
3031
import ghidra.framework.plugintool.PluginTool;
@@ -247,7 +248,32 @@ public static Program getProgramByPath(String programPath) {
247248
return null;
248249
}
249250

250-
DomainFile domainFile = project.getProjectData().getRootFolder().getFile(programPath);
251+
// Handle paths that may include folders (e.g., "/imported/program.exe")
252+
DomainFile domainFile = null;
253+
254+
if (programPath.startsWith("/")) {
255+
// Remove leading slash for processing
256+
String relativePath = programPath.substring(1);
257+
int lastSlash = relativePath.lastIndexOf('/');
258+
259+
if (lastSlash > 0) {
260+
// Path contains folders - need to get the folder first, then the file
261+
String folderPath = "/" + relativePath.substring(0, lastSlash);
262+
String fileName = relativePath.substring(lastSlash + 1);
263+
264+
DomainFolder folder = project.getProjectData().getFolder(folderPath);
265+
if (folder != null) {
266+
domainFile = folder.getFile(fileName);
267+
}
268+
} else {
269+
// No folders, file is directly in root
270+
domainFile = project.getProjectData().getRootFolder().getFile(relativePath);
271+
}
272+
} else {
273+
// Handle paths without leading slash
274+
domainFile = project.getProjectData().getRootFolder().getFile(programPath);
275+
}
276+
251277
if (domainFile == null) {
252278
Msg.warn(RevaProgramManager.class, "Could not find program: " + programPath);
253279
return null;

src/main/java/reva/tools/project/ProjectToolProvider.java

Lines changed: 110 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
3030
import ghidra.app.services.ProgramManager;
3131
import ghidra.app.util.importer.AutoImporter;
32+
import ghidra.app.util.opinion.LoadException;
3233
import ghidra.app.util.importer.MessageLog;
3334
import ghidra.app.util.opinion.LoadResults;
3435
import ghidra.app.util.opinion.Loaded;
@@ -47,6 +48,7 @@
4748
import ghidra.framework.plugintool.PluginTool;
4849
import ghidra.program.model.lang.CompilerSpec;
4950
import ghidra.program.model.lang.CompilerSpecID;
51+
import ghidra.util.Msg;
5052
import ghidra.program.model.lang.Language;
5153
import ghidra.program.model.lang.LanguageCompilerSpecPair;
5254
import ghidra.program.model.lang.LanguageDescription;
@@ -694,7 +696,10 @@ private void registerImportProgramTool() throws McpError {
694696
"Where to save the programs in the project (default: /)", "/"
695697
));
696698
properties.put("processorSpec", SchemaUtil.stringProperty(
697-
"Optional processor/compiler spec (e.g., 'x86:LE:64:default', 'golang:BE:64:default'). If not specified, Ghidra will auto-detect."
699+
"Optional processor/compiler spec hint (e.g., 'x86:LE:64:default'). " +
700+
"If not specified, Ghidra will automatically detect the best match. " +
701+
"Only use if auto-detection chooses incorrectly. " +
702+
"The actual loadspec used will be returned in the response."
698703
));
699704
properties.put("runAnalysis", SchemaUtil.booleanPropertyWithDefault(
700705
"Whether to run auto-analysis after loading", true
@@ -1111,29 +1116,25 @@ private McpSchema.CallToolResult importMultipleFiles(List<File> files, String pr
11111116
int failureCount = 0;
11121117

11131118
for (File file : files) {
1114-
try {
1115-
Map<String, Object> result = importSingleFile(file, folder, processor, runAnalysis, openProgram);
1116-
results.add(result);
1117-
if (result.get("success").equals(true)) {
1118-
successCount++;
1119-
} else {
1120-
failureCount++;
1121-
}
1122-
} catch (Exception e) {
1123-
Map<String, Object> errorResult = new HashMap<>();
1124-
errorResult.put("success", false);
1125-
errorResult.put("filePath", file.getAbsolutePath());
1126-
errorResult.put("error", e.getMessage());
1127-
results.add(errorResult);
1119+
Map<String, Object> result = importSingleFile(file, folder, processor, runAnalysis, openProgram);
1120+
results.add(result);
1121+
1122+
if (Boolean.TRUE.equals(result.get("success"))) {
1123+
successCount++;
1124+
} else {
11281125
failureCount++;
1126+
// Log the failure but continue with other files
1127+
String error = (String) result.get("error");
1128+
Msg.warn(this, "Failed to import " + file.getName() + ": " + error);
11291129
}
11301130
}
11311131

11321132
Map<String, Object> response = new HashMap<>();
1133-
response.put("success", true);
1133+
response.put("success", successCount > 0); // Success if at least one file imported
11341134
response.put("sourceType", sourceType);
11351135
response.put("sourcePath", sourcePath);
11361136
response.put("isListing", false);
1137+
response.put("projectPath", projectPath);
11371138
response.put("totalFiles", files.size());
11381139
response.put("successCount", successCount);
11391140
response.put("failureCount", failureCount);
@@ -1160,29 +1161,25 @@ private McpSchema.CallToolResult importMultipleFilesFromArchive(List<FSRL> files
11601161
int failureCount = 0;
11611162

11621163
for (FSRL fileFsrl : files) {
1163-
try {
1164-
Map<String, Object> result = importSingleFileFromArchive(fileFsrl, folder, processor, runAnalysis, openProgram);
1165-
results.add(result);
1166-
if (result.get("success").equals(true)) {
1167-
successCount++;
1168-
} else {
1169-
failureCount++;
1170-
}
1171-
} catch (Exception e) {
1172-
Map<String, Object> errorResult = new HashMap<>();
1173-
errorResult.put("success", false);
1174-
errorResult.put("filePath", fileFsrl.toString());
1175-
errorResult.put("error", e.getMessage());
1176-
results.add(errorResult);
1164+
Map<String, Object> result = importSingleFileFromArchive(fileFsrl, folder, processor, runAnalysis, openProgram);
1165+
results.add(result);
1166+
1167+
if (Boolean.TRUE.equals(result.get("success"))) {
1168+
successCount++;
1169+
} else {
11771170
failureCount++;
1171+
// Log the failure but continue with other files
1172+
String error = (String) result.get("error");
1173+
Msg.warn(this, "Failed to import " + fileFsrl.getName() + ": " + error);
11781174
}
11791175
}
11801176

11811177
Map<String, Object> response = new HashMap<>();
1182-
response.put("success", true);
1178+
response.put("success", successCount > 0); // Success if at least one file imported
11831179
response.put("sourceType", "archive");
11841180
response.put("sourcePath", sourcePath);
11851181
response.put("isListing", false);
1182+
response.put("projectPath", projectPath);
11861183
response.put("totalFiles", files.size());
11871184
response.put("successCount", successCount);
11881185
response.put("failureCount", failureCount);
@@ -1201,16 +1198,35 @@ private Map<String, Object> importSingleFile(File file, DomainFolder folder, Lan
12011198
String baseName = file.getName();
12021199
String programName = createUniqueFileName(folder, baseName);
12031200

1204-
// Use AutoImporter to import the file
12051201
Project project = AppInfo.getActiveProject();
12061202
MessageLog messageLog = new MessageLog();
1207-
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(
1208-
file, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1203+
LoadResults<Program> loadResults;
1204+
1205+
// Use the processor spec if provided, otherwise auto-detect
1206+
if (processor != null) {
1207+
try {
1208+
Language language = processor.getLanguage();
1209+
CompilerSpec compilerSpec = processor.getCompilerSpec();
1210+
loadResults = AutoImporter.importByLookingForLcs(
1211+
file, project, folder.getPathname() + "/" + programName,
1212+
language, compilerSpec, this, messageLog, TaskMonitor.DUMMY);
1213+
} catch (Exception e) {
1214+
// If specific processor spec fails, fall back to auto-detect
1215+
Msg.warn(this, "Processor spec " + processor + " failed for " + file.getName() +
1216+
", falling back to auto-detection: " + e.getMessage());
1217+
loadResults = AutoImporter.importByUsingBestGuess(
1218+
file, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1219+
}
1220+
} else {
1221+
loadResults = AutoImporter.importByUsingBestGuess(
1222+
file, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1223+
}
12091224

12101225
if (loadResults == null || loadResults.size() == 0) {
12111226
result.put("success", false);
12121227
result.put("filePath", file.getAbsolutePath());
12131228
result.put("error", "No programs were imported from file");
1229+
result.put("errorType", "NO_PROGRAMS_IMPORTED");
12141230
return result;
12151231
}
12161232

@@ -1221,6 +1237,11 @@ private Map<String, Object> importSingleFile(File file, DomainFolder folder, Lan
12211237
Program program = loadResults.getPrimaryDomainObject();
12221238
String programPath = program.getDomainFile().getPathname();
12231239

1240+
// Extract the actual loadspec used
1241+
String languageId = program.getLanguage().getLanguageID().getIdAsString();
1242+
String compilerSpecId = program.getCompilerSpec().getCompilerSpecID().getIdAsString();
1243+
String loadSpec = languageId + ":" + compilerSpecId;
1244+
12241245
// Run analysis on all loaded programs if requested
12251246
if (runAnalysis) {
12261247
for (Loaded<Program> loaded : loadResults) {
@@ -1232,6 +1253,7 @@ private Map<String, Object> importSingleFile(File file, DomainFolder folder, Lan
12321253
} catch (Exception e) {
12331254
loadedProgram.endTransaction(analysisTransactionID, false);
12341255
// Log but don't fail the import due to analysis issues
1256+
Msg.warn(this, "Analysis failed for " + loadedProgram.getName() + ": " + e.getMessage());
12351257
}
12361258
}
12371259
}
@@ -1247,15 +1269,26 @@ private Map<String, Object> importSingleFile(File file, DomainFolder folder, Lan
12471269
result.put("filePath", file.getAbsolutePath());
12481270
result.put("programPath", programPath);
12491271
result.put("programName", programName);
1272+
result.put("languageId", languageId);
1273+
result.put("compilerSpecId", compilerSpecId);
1274+
result.put("loadSpec", loadSpec);
12501275
result.put("totalProgramsImported", loadResults.size());
12511276

1252-
// Release the LoadResults (this will release all loaded programs)
1253-
loadResults.release(this);
1277+
// Don't release the LoadResults immediately - let programs stay open for use
1278+
// This fixes the "program not found after import" bug
1279+
// loadResults.release(this); // REMOVED
12541280

1281+
} catch (LoadException e) {
1282+
// Specific handling for files that can't be loaded
1283+
result.put("success", false);
1284+
result.put("filePath", file.getAbsolutePath());
1285+
result.put("error", "Could not find a suitable loader for this file: " + e.getMessage());
1286+
result.put("errorType", "NO_LOADER");
12551287
} catch (Exception e) {
12561288
result.put("success", false);
12571289
result.put("filePath", file.getAbsolutePath());
12581290
result.put("error", e.getMessage());
1291+
result.put("errorType", e.getClass().getSimpleName());
12591292
}
12601293

12611294
return result;
@@ -1271,16 +1304,35 @@ private Map<String, Object> importSingleFileFromArchive(FSRL fileFsrl, DomainFol
12711304
String fileName = fileFsrl.getName();
12721305
String programName = createUniqueFileName(folder, fileName);
12731306

1274-
// Use AutoImporter to import from archive
12751307
Project project = AppInfo.getActiveProject();
12761308
MessageLog messageLog = new MessageLog();
1277-
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(
1278-
fileFsrl, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1309+
LoadResults<Program> loadResults;
1310+
1311+
// Use the processor spec if provided, otherwise auto-detect
1312+
if (processor != null) {
1313+
try {
1314+
Language language = processor.getLanguage();
1315+
CompilerSpec compilerSpec = processor.getCompilerSpec();
1316+
loadResults = AutoImporter.importByLookingForLcs(
1317+
fileFsrl, project, folder.getPathname() + "/" + programName,
1318+
language, compilerSpec, this, messageLog, TaskMonitor.DUMMY);
1319+
} catch (Exception e) {
1320+
// If specific processor spec fails, fall back to auto-detect
1321+
Msg.warn(this, "Processor spec " + processor + " failed for " + fileName +
1322+
", falling back to auto-detection: " + e.getMessage());
1323+
loadResults = AutoImporter.importByUsingBestGuess(
1324+
fileFsrl, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1325+
}
1326+
} else {
1327+
loadResults = AutoImporter.importByUsingBestGuess(
1328+
fileFsrl, project, folder.getPathname() + "/" + programName, this, messageLog, TaskMonitor.DUMMY);
1329+
}
12791330

12801331
if (loadResults == null || loadResults.size() == 0) {
12811332
result.put("success", false);
12821333
result.put("filePath", fileFsrl.toString());
12831334
result.put("error", "No programs were imported from archive file");
1335+
result.put("errorType", "NO_PROGRAMS_IMPORTED");
12841336
return result;
12851337
}
12861338

@@ -1291,6 +1343,11 @@ private Map<String, Object> importSingleFileFromArchive(FSRL fileFsrl, DomainFol
12911343
Program program = loadResults.getPrimaryDomainObject();
12921344
String programPath = program.getDomainFile().getPathname();
12931345

1346+
// Extract the actual loadspec used
1347+
String languageId = program.getLanguage().getLanguageID().getIdAsString();
1348+
String compilerSpecId = program.getCompilerSpec().getCompilerSpecID().getIdAsString();
1349+
String loadSpec = languageId + ":" + compilerSpecId;
1350+
12941351
// Run analysis on all loaded programs if requested
12951352
if (runAnalysis) {
12961353
for (Loaded<Program> loaded : loadResults) {
@@ -1302,6 +1359,7 @@ private Map<String, Object> importSingleFileFromArchive(FSRL fileFsrl, DomainFol
13021359
} catch (Exception e) {
13031360
loadedProgram.endTransaction(analysisTransactionID, false);
13041361
// Log but don't fail the import due to analysis issues
1362+
Msg.warn(this, "Analysis failed for " + loadedProgram.getName() + ": " + e.getMessage());
13051363
}
13061364
}
13071365
}
@@ -1317,15 +1375,26 @@ private Map<String, Object> importSingleFileFromArchive(FSRL fileFsrl, DomainFol
13171375
result.put("filePath", fileFsrl.toString());
13181376
result.put("programPath", programPath);
13191377
result.put("programName", programName);
1378+
result.put("languageId", languageId);
1379+
result.put("compilerSpecId", compilerSpecId);
1380+
result.put("loadSpec", loadSpec);
13201381
result.put("totalProgramsImported", loadResults.size());
13211382

1322-
// Release the LoadResults (this will release all loaded programs)
1323-
loadResults.release(this);
1383+
// Don't release the LoadResults immediately - let programs stay open for use
1384+
// This fixes the "program not found after import" bug
1385+
// loadResults.release(this); // REMOVED
13241386

1387+
} catch (LoadException e) {
1388+
// Specific handling for files that can't be loaded
1389+
result.put("success", false);
1390+
result.put("filePath", fileFsrl.toString());
1391+
result.put("error", "Could not find a suitable loader for this file: " + e.getMessage());
1392+
result.put("errorType", "NO_LOADER");
13251393
} catch (Exception e) {
13261394
result.put("success", false);
13271395
result.put("filePath", fileFsrl.toString());
13281396
result.put("error", e.getMessage());
1397+
result.put("errorType", e.getClass().getSimpleName());
13291398
}
13301399

13311400
return result;

0 commit comments

Comments
 (0)