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
@@ -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) { if (bundleHost != null) {
throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); throw new RuntimeException("GhidraScriptUtil initialized multiple times!");
} }
@@ -79,22 +79,6 @@ public class GhidraScriptUtil {
catch (OSGiException | IOException e) { catch (OSGiException | IOException e) {
Msg.error(GhidraScriptUtil.class, "Failed to initialize BundleHost", 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(getUserScriptDirectory(), true, false);
bundleHost.add(getSystemScriptDirectories(), true, true); bundleHost.add(getSystemScriptDirectories(), true, true);
@@ -103,7 +87,7 @@ public class GhidraScriptUtil {
/** /**
* dispose of the bundle host and providers list * dispose of the bundle host and providers list
*/ */
public static void dispose() { private static void dispose() {
if (bundleHost != null) { if (bundleHost != null) {
bundleHost.stopFramework(); bundleHost.stopFramework();
bundleHost = null; bundleHost = null;
@@ -455,7 +439,7 @@ public class GhidraScriptUtil {
*/ */
public static BundleHost acquireBundleHostReference() { public static BundleHost acquireBundleHostReference() {
if (referenceCount.getAndIncrement() == 0) { if (referenceCount.getAndIncrement() == 0) {
initialize(new BundleHost(), null); initialize(new BundleHost());
} }
return bundleHost; return bundleHost;
} }
@@ -16,12 +16,10 @@
package ghidra.app.util.headless; package ghidra.app.util.headless;
import java.io.*; import java.io.*;
import java.util.List;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.GhidraApplicationLayout; import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable; import ghidra.GhidraLaunchable;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.HeadlessGhidraApplicationConfiguration; import ghidra.framework.HeadlessGhidraApplicationConfiguration;
@@ -36,7 +34,6 @@ import utility.application.ApplicationLayout;
*/ */
public class GhidraScriptRunner implements GhidraLaunchable { public class GhidraScriptRunner implements GhidraLaunchable {
private List<String> scriptPaths;
private String propertiesFilePath; private String propertiesFilePath;
@Override @Override
@@ -47,13 +44,13 @@ public class GhidraScriptRunner implements GhidraLaunchable {
System.exit(0); System.exit(0);
} }
String logFile = null; //TODO get from arguments? String logFile = null; //TODO get from arguments?
GhidraScriptUtil.initialize(new BundleHost(), scriptPaths); GhidraScriptUtil.acquireBundleHostReference();
try { try {
initialize(layout, logFile, true); initialize(layout, logFile, true);
runScript(args[0]); runScript(args[0]);
} }
finally { finally {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
} }
@@ -303,8 +303,8 @@ public class HeadlessAnalyzer {
} }
} }
List<String> parsedScriptPaths = parseScriptPaths(options.scriptPaths); BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
try { try {
showConfiguredScriptPaths(); showConfiguredScriptPaths();
compileScripts(); compileScripts();
@@ -365,7 +365,7 @@ public class HeadlessAnalyzer {
throw new IOException(e); // unexpected throw new IOException(e); // unexpected
} }
finally { finally {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
} }
@@ -418,8 +418,8 @@ public class HeadlessAnalyzer {
} }
} }
List<String> parsedScriptPaths = parseScriptPaths(options.scriptPaths); BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
try { try {
showConfiguredScriptPaths(); showConfiguredScriptPaths();
compileScripts(); compileScripts();
@@ -471,7 +471,7 @@ public class HeadlessAnalyzer {
} }
} }
finally { 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) { if (scriptPaths == null) {
return null; return List.of();
} }
List<String> parsedScriptPaths = new ArrayList<>(); List<ResourceFile> parsedScriptPaths = new ArrayList<>();
for (String path : scriptPaths) { for (String path : scriptPaths) {
ResourceFile pathFile = Path.fromPathString(path); ResourceFile pathFile = Path.fromPathString(path);
String absPath = pathFile.getAbsolutePath();
if (pathFile.exists()) { if (pathFile.exists()) {
parsedScriptPaths.add(absPath); parsedScriptPaths.add(pathFile);
} }
else { else {
Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + pathFile);
Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + absPath);
} }
} }
return parsedScriptPaths; return parsedScriptPaths;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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 generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.CodeCompletion; import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -72,7 +71,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get(); interpreter = GhidraJythonInterpreter.get();
executeJythonProgram(simpleTestProgram); executeJythonProgram(simpleTestProgram);
} }
@@ -80,7 +79,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
interpreter.cleanup(); interpreter.cleanup();
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
@Test @Test
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,7 +22,6 @@ import java.io.ByteArrayOutputStream;
import org.junit.*; import org.junit.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -37,7 +36,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
out = new ByteArrayOutputStream(); out = new ByteArrayOutputStream();
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get(); interpreter = GhidraJythonInterpreter.get();
interpreter.setOut(out); interpreter.setOut(out);
interpreter.setErr(out); interpreter.setErr(out);
@@ -47,7 +46,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
public void tearDown() throws Exception { public void tearDown() throws Exception {
out.reset(); out.reset();
interpreter.cleanup(); interpreter.cleanup();
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
/** /**
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -19,7 +19,6 @@ import static org.junit.Assert.*;
import org.junit.*; import org.junit.*;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -38,14 +37,14 @@ public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception { public void setUp() throws Exception {
env = new TestEnv(); env = new TestEnv();
tool = env.getTool(); tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(JythonPlugin.class.getName()); tool.addPlugin(JythonPlugin.class.getName());
plugin = env.getPlugin(JythonPlugin.class); plugin = env.getPlugin(JythonPlugin.class);
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
env.dispose(); env.dispose();
} }
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,7 +26,6 @@ import javax.swing.KeyStroke;
import org.junit.*; import org.junit.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo; import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,7 +34,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (Files.notExists(userScriptDir)) { if (Files.notExists(userScriptDir)) {
Files.createDirectories(userScriptDir); Files.createDirectories(userScriptDir);
@@ -44,7 +43,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
@Test @Test
@@ -46,14 +46,14 @@ public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception { public void setUp() throws Exception {
env = new TestEnv(); env = new TestEnv();
tool = env.getTool(); tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(ConsolePlugin.class.getName()); tool.addPlugin(ConsolePlugin.class.getName());
console = tool.getService(ConsoleService.class); console = tool.getService(ConsoleService.class);
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
env.dispose(); env.dispose();
} }
@@ -18,7 +18,6 @@ package ghidra.pyghidra;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,14 +34,14 @@ public class PyGhidraPluginTest extends AbstractGhidraHeadedIntegrationTest {
public void setUp() throws Exception { public void setUp() throws Exception {
env = new TestEnv(); env = new TestEnv();
PluginTool tool = env.getTool(); PluginTool tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
tool.addPlugin(PyGhidraPlugin.class.getName()); tool.addPlugin(PyGhidraPlugin.class.getName());
env.getPlugin(PyGhidraPlugin.class); env.getPlugin(PyGhidraPlugin.class);
} }
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
env.dispose(); env.dispose();
} }
} }
@@ -26,7 +26,6 @@ import javax.swing.KeyStroke;
import org.junit.*; import org.junit.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo; import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@@ -35,7 +34,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null); GhidraScriptUtil.acquireBundleHostReference();
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (Files.notExists(userScriptDir)) { if (Files.notExists(userScriptDir)) {
Files.createDirectories(userScriptDir); Files.createDirectories(userScriptDir);
@@ -44,7 +43,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
GhidraScriptUtil.dispose(); GhidraScriptUtil.releaseBundleHostReference();
} }
@Test @Test