GP-6132: Fixed a bug in the the PyGhidra headless analyzer that resulted in the wrong exception being thrown when a script tries to import a module that isn't found

This commit is contained in:
Ryan Kurtz
2025-11-18 14:15:44 -05:00
parent 72cb11adfb
commit 0d52943d1f
10 changed files with 40 additions and 67 deletions

View File

@@ -63,11 +63,11 @@ public class GhidraScriptUtil {
}
/**
* set the bundle host and start the framework
* Initialize state of GhidraScriptUtil with user and system paths
*
* @param aBundleHost the bundle host
* @param aBundleHost the host to use
*/
private static void setBundleHost(BundleHost aBundleHost) {
private static void initialize(BundleHost aBundleHost) {
if (bundleHost != null) {
throw new RuntimeException("GhidraScriptUtil initialized multiple times!");
}
@@ -79,22 +79,6 @@ public class GhidraScriptUtil {
catch (OSGiException | IOException e) {
Msg.error(GhidraScriptUtil.class, "Failed to initialize BundleHost", e);
}
}
/**
* Initialize state of GhidraScriptUtil with user, system, and optional extra system paths.
*
* @param aBundleHost the host to use
* @param extraSystemPaths additional system paths for this run, can be null
*
*/
public static void initialize(BundleHost aBundleHost, List<String> extraSystemPaths) {
setBundleHost(aBundleHost);
if (extraSystemPaths != null) {
for (String path : extraSystemPaths) {
bundleHost.add(new ResourceFile(path), true, true);
}
}
bundleHost.add(getUserScriptDirectory(), true, false);
bundleHost.add(getSystemScriptDirectories(), true, true);
@@ -103,7 +87,7 @@ public class GhidraScriptUtil {
/**
* dispose of the bundle host and providers list
*/
public static void dispose() {
private static void dispose() {
if (bundleHost != null) {
bundleHost.stopFramework();
bundleHost = null;
@@ -455,7 +439,7 @@ public class GhidraScriptUtil {
*/
public static BundleHost acquireBundleHostReference() {
if (referenceCount.getAndIncrement() == 0) {
initialize(new BundleHost(), null);
initialize(new BundleHost());
}
return bundleHost;
}

View File

@@ -16,12 +16,10 @@
package ghidra.app.util.headless;
import java.io.*;
import java.util.List;
import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.*;
import ghidra.framework.Application;
import ghidra.framework.HeadlessGhidraApplicationConfiguration;
@@ -36,7 +34,6 @@ import utility.application.ApplicationLayout;
*/
public class GhidraScriptRunner implements GhidraLaunchable {
private List<String> scriptPaths;
private String propertiesFilePath;
@Override
@@ -47,13 +44,13 @@ public class GhidraScriptRunner implements GhidraLaunchable {
System.exit(0);
}
String logFile = null; //TODO get from arguments?
GhidraScriptUtil.initialize(new BundleHost(), scriptPaths);
GhidraScriptUtil.acquireBundleHostReference();
try {
initialize(layout, logFile, true);
runScript(args[0]);
}
finally {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
}

View File

@@ -303,8 +303,8 @@ public class HeadlessAnalyzer {
}
}
List<String> parsedScriptPaths = parseScriptPaths(options.scriptPaths);
GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths);
BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
try {
showConfiguredScriptPaths();
compileScripts();
@@ -365,7 +365,7 @@ public class HeadlessAnalyzer {
throw new IOException(e); // unexpected
}
finally {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
}
@@ -418,8 +418,8 @@ public class HeadlessAnalyzer {
}
}
List<String> parsedScriptPaths = parseScriptPaths(options.scriptPaths);
GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths);
BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
try {
showConfiguredScriptPaths();
compileScripts();
@@ -471,7 +471,7 @@ public class HeadlessAnalyzer {
}
}
finally {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
}
@@ -693,20 +693,18 @@ public class HeadlessAnalyzer {
}
}
private List<String> parseScriptPaths(List<String> scriptPaths) {
private List<ResourceFile> parseScriptPaths(List<String> scriptPaths) {
if (scriptPaths == null) {
return null;
return List.of();
}
List<String> parsedScriptPaths = new ArrayList<>();
List<ResourceFile> parsedScriptPaths = new ArrayList<>();
for (String path : scriptPaths) {
ResourceFile pathFile = Path.fromPathString(path);
String absPath = pathFile.getAbsolutePath();
if (pathFile.exists()) {
parsedScriptPaths.add(absPath);
parsedScriptPaths.add(pathFile);
}
else {
Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + absPath);
Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + pathFile);
}
}
return parsedScriptPaths;

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,7 +29,6 @@ import org.junit.rules.TemporaryFolder;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -72,7 +71,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes
@Before
public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get();
executeJythonProgram(simpleTestProgram);
}
@@ -80,7 +79,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes
@After
public void tearDown() throws Exception {
interpreter.cleanup();
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
@Test

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,7 +22,6 @@ import java.io.ByteArrayOutputStream;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -37,7 +36,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
out = new ByteArrayOutputStream();
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get();
interpreter.setOut(out);
interpreter.setErr(out);
@@ -47,7 +46,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
public void tearDown() throws Exception {
out.reset();
interpreter.cleanup();
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
/**

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -19,7 +19,6 @@ import static org.junit.Assert.*;
import org.junit.*;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -38,14 +37,14 @@ public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(JythonPlugin.class.getName());
plugin = env.getPlugin(JythonPlugin.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
env.dispose();
}

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,7 +26,6 @@ import javax.swing.KeyStroke;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,7 +34,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (Files.notExists(userScriptDir)) {
Files.createDirectories(userScriptDir);
@@ -44,7 +43,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
@Test

View File

@@ -46,14 +46,14 @@ public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(ConsolePlugin.class.getName());
console = tool.getService(ConsoleService.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
env.dispose();
}

View File

@@ -18,7 +18,6 @@ package ghidra.pyghidra;
import org.junit.After;
import org.junit.Before;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,14 +34,14 @@ public class PyGhidraPluginTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception {
env = new TestEnv();
PluginTool tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(PyGhidraPlugin.class.getName());
env.getPlugin(PyGhidraPlugin.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
env.dispose();
}
}

View File

@@ -26,7 +26,6 @@ import javax.swing.KeyStroke;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,7 +34,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null);
GhidraScriptUtil.acquireBundleHostReference();
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (Files.notExists(userScriptDir)) {
Files.createDirectories(userScriptDir);
@@ -44,7 +43,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
GhidraScriptUtil.releaseBundleHostReference();
}
@Test