From 131aa4ac9aea78ef63710a6b943e5f0f987adbe5 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Thu, 2 Oct 2025 12:42:24 -0400 Subject: [PATCH] GP-0: If pyghidra.sys.modules.restore is set, PyGhidra will restore sys.modules each time a script is run (Closes #8529) --- Ghidra/Features/PyGhidra/src/main/py/README.md | 6 +++++- .../Features/PyGhidra/src/main/py/src/pyghidra/script.py | 7 +++++++ Ghidra/RuntimeScripts/Common/support/launch.properties | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Ghidra/Features/PyGhidra/src/main/py/README.md b/Ghidra/Features/PyGhidra/src/main/py/README.md index 8f128cf9e2..1daf1b703d 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/README.md +++ b/Ghidra/Features/PyGhidra/src/main/py/README.md @@ -558,8 +558,12 @@ import pdb_ # imports Ghidra's pdb ``` ## Change History __3.0.0:__ -* Revised the the PyGhidra API. See the [API section](#api) for more details. +* Revised the PyGhidra API. See the [API section](#api) for more details. * PyGhidra 3.0.0 requires Ghidra 12.0 or later to run. +* If PyGhidra sees that the `pyghidra.sys.modules.restore` Java system property is set (typically + via the `support/launch.properties` file), it will restore `sys.modules` to its prior state after + a PyGhidra script is run so the next time the script is run, it freshly loads all of its imported + modules again. This is experimental and should only be enabled if necessary. __2.2.1:__ * PyGhidra now launches with the current working directory removed from `sys.path` to prevent diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/script.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/script.py index 3bda666a8b..e272620277 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/script.py +++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/script.py @@ -219,6 +219,8 @@ class PyGhidraScript(dict): :param script_path: The path of the python script :param script_args: The arguments for the python script """ + from java.lang import Boolean # type:ignore @UnresolvedImport + sf = self._script.getSourceFile() if sf is None and script_path is None: return @@ -232,6 +234,7 @@ class PyGhidraScript(dict): self._script.setScriptArgs(script_args) orig_argv = sys.argv + orig_modules = sys.modules.copy() script_root = str(Path(script_path).parent) # honor the python safe_path flag introduced in 3.11 @@ -267,6 +270,10 @@ class PyGhidraScript(dict): finally: sys.argv = orig_argv + if Boolean.getBoolean("pyghidra.sys.modules.restore"): + for k in list(set(sys.modules) - set(orig_modules)): + sys.modules.pop(k, None) + if not safe_path: sys.path.remove(script_root) diff --git a/Ghidra/RuntimeScripts/Common/support/launch.properties b/Ghidra/RuntimeScripts/Common/support/launch.properties index 6ab383cf93..a785a72bcc 100644 --- a/Ghidra/RuntimeScripts/Common/support/launch.properties +++ b/Ghidra/RuntimeScripts/Common/support/launch.properties @@ -151,3 +151,8 @@ VMARGS=--enable-native-access=ALL-UNNAMED # Disables the loaders echoing their message log to the application.log file #VMARGS=-Ddisable.loader.logging=true + +# Restores sys.modules to its prior state after a PyGhidra script is run so the next time the script +# is run, it freshly reloads all of its imported modules again. This is experimental and should only +# be enabled if necessary. +#VMARGS=-Dpyghidra.sys.modules.restore=true