diff --git a/data/python/jepeval.py b/data/python/jepeval.py index a60bcbf..4b22d7a 100644 --- a/data/python/jepeval.py +++ b/data/python/jepeval.py @@ -14,6 +14,10 @@ jepeval_lines = [] +class InterpreterExit(Exception): + """Signal to Java that the interpreter should exit""" + pass + def jepeval(line): """attempt to compile and eval a given Python statement @@ -73,7 +77,8 @@ def _jepeval(line): try: more_input_needed = _jepeval(line) except SystemExit as err: - more_input_needed = False + raise InterpreterExit() + # more_input_needed = False except Exception as err: # Python exceptions are printed in Python instead of Java to improve error messaging # in the Ghidra console window diff --git a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java index b3d6091..7427091 100644 --- a/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java +++ b/src/main/java/ghidrathon/GhidrathonConsoleInputThread.java @@ -25,6 +25,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.SwingUtilities; public class GhidrathonConsoleInputThread extends Thread { @@ -159,6 +160,13 @@ private boolean evalPython(String line) throws RuntimeException { new PrintWriter(console.getStdOut())); status = python.eval(line, interactiveScript); + } catch (GhidrathonInterpreterExitException e) { + // Gracefully shut down only the interpreter + dispose(); // Stop the input thread + SwingUtilities.invokeLater(() -> { + plugin.shutdownInterpreter(); + }); + return false; } finally { interactiveScript.end(false); } diff --git a/src/main/java/ghidrathon/GhidrathonInterpreterExitException.java b/src/main/java/ghidrathon/GhidrathonInterpreterExitException.java new file mode 100644 index 0000000..82a1c21 --- /dev/null +++ b/src/main/java/ghidrathon/GhidrathonInterpreterExitException.java @@ -0,0 +1,7 @@ +package ghidrathon; + +public class GhidrathonInterpreterExitException extends RuntimeException { + public GhidrathonInterpreterExitException() { + super("Ghidrathon interpreter requested exit"); + } +} diff --git a/src/main/java/ghidrathon/GhidrathonPlugin.java b/src/main/java/ghidrathon/GhidrathonPlugin.java index 85e3a8d..f69fb37 100644 --- a/src/main/java/ghidrathon/GhidrathonPlugin.java +++ b/src/main/java/ghidrathon/GhidrathonPlugin.java @@ -159,6 +159,31 @@ private void resetInterpreterInBackground() { inputThread.start(); } + void shutdownInterpreter() { + if (inputThread != null) { + inputThread.dispose(); + inputThread = null; + } + + if (console != null) { + console.dispose(); + console = null; + } + + interactiveScript = null; + interactiveTaskMonitor = null; + + // Recreate a new console instance, so Ghidra re-registers it + SwingUtilities.invokeLater(() -> { + console = getTool() + .getService(InterpreterPanelService.class) + .createInterpreterPanel(this, false); + + // Let user activate manually + console.addFirstActivationCallback(() -> resetInterpreter()); + }); + } + class PythonInteractiveTaskMonitor extends TaskMonitorAdapter { private PrintWriter output = null; diff --git a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java index bcaded8..2a0786a 100644 --- a/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java +++ b/src/main/java/ghidrathon/interpreter/GhidrathonInterpreter.java @@ -632,6 +632,11 @@ public boolean eval(String line, GhidrathonScript script) { } catch (JepException e) { + // Check for custom Python-side exception `InterpreterExit` + if (e.getMessage() != null && e.getMessage().contains("InterpreterExit")) { + throw new ghidrathon.GhidrathonInterpreterExitException(); // Throw signal Java-side + } + // Python exceptions should be handled in Python land; something bad must have happened e.printStackTrace(this.err); throw new RuntimeException(e);