From 1add5a9bcead1da045ff5bf225f3874f35970394 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Mon, 9 Jun 2025 12:14:58 -0400 Subject: [PATCH] GP-5737: Fixing GhidraScript PrintWriter --- .../Base/ghidra_scripts/HelloWorldScript.java | 4 +- .../app/plugin/core/script/RunScriptTask.java | 9 +- .../java/ghidra/app/script/GhidraScript.java | 241 ++++++++++-------- .../app/script/GhidraScriptProvider.java | 8 +- .../ghidra/app/script/ScriptControls.java | 133 ++++++++++ .../app/util/headless/GhidraScriptRunner.java | 9 +- .../app/util/headless/HeadlessAnalyzer.java | 15 +- .../app/util/headless/HeadlessScript.java | 8 +- .../src/main/java/ghidra/test/TestEnv.java | 6 +- .../AbstractGhidraScriptMgrPluginTest.java | 17 +- .../core/script/BundleStatusManagerTest.java | 2 +- .../script/GhidraScriptMgrPlugin2Test.java | 6 +- .../script/GhidraScriptMgrPlugin3Test.java | 4 +- .../script/GhidraScriptAskMethodsTest.java | 3 +- .../script/GhidraScriptRealProgramTest.java | 3 +- .../ghidra/app/script/GhidraScriptTest.java | 7 +- .../ghidra_scripts/ShowConstantUse.java | 4 +- .../taint/RunPCodeExportScriptTask.java | 6 +- .../main/java/ghidra/jython/JythonPlugin.java | 11 +- .../jython/JythonPluginExecutionThread.java | 15 +- .../main/java/ghidra/jython/JythonScript.java | 10 +- .../java/ghidra/jython/JythonScriptTest.java | 9 +- .../WindowsResourceReferenceAnalyzer.java | 19 +- .../java/ghidra/pyghidra/PyGhidraPlugin.java | 3 +- .../pyghidra/PyGhidraScriptProvider.java | 8 +- .../interpreter/InterpreterGhidraScript.java | 5 +- 26 files changed, 359 insertions(+), 206 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java diff --git a/Ghidra/Features/Base/ghidra_scripts/HelloWorldScript.java b/Ghidra/Features/Base/ghidra_scripts/HelloWorldScript.java index e12cf5934d..ee5d5c9db1 100644 --- a/Ghidra/Features/Base/ghidra_scripts/HelloWorldScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/HelloWorldScript.java @@ -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. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/RunScriptTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/RunScriptTask.java index c774de4afa..2386f10cc2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/RunScriptTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/RunScriptTask.java @@ -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. @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.script; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraState; +import ghidra.app.script.*; import ghidra.app.services.ConsoleService; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -44,7 +43,7 @@ class RunScriptTask extends Task { Thread.currentThread().setName(scriptName); console.addMessage(scriptName, "Running..."); - script.execute(currentState, monitor, console.getStdOut()); + script.execute(currentState, new ScriptControls(console, monitor)); console.addMessage(scriptName, "Finished!"); } catch (CancelledException e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 200c20416e..2bdd731345 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -37,7 +37,8 @@ import ghidra.app.plugin.core.analysis.AnalysisWorker; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.table.TableComponentProvider; -import ghidra.app.services.*; +import ghidra.app.services.GoToService; +import ghidra.app.services.ProgramManager; import ghidra.app.tablechooser.TableChooserDialog; import ghidra.app.tablechooser.TableChooserExecutor; import ghidra.app.util.demangler.DemangledObject; @@ -142,6 +143,8 @@ public abstract class GhidraScript extends FlatProgramAPI { protected ResourceFile sourceFile; protected GhidraState state; protected PrintWriter writer; + protected PrintWriter errorWriter; + protected boolean decorateOutput; protected Address currentAddress; protected ProgramLocation currentLocation; protected ProgramSelection currentSelection; @@ -192,18 +195,47 @@ public abstract class GhidraScript extends FlatProgramAPI { /** * Set the context for this script. + *

+ * This method will use the given {@link PrintWriter} for both {@code stdout} and + * {@code stderr}. * * @param state state object * @param monitor the monitor to use during run - * @param writer the target of script "print" statements + * @param writer the target of script "print" statements (may be null) + * @deprecated Use {@link #set(GhidraState)} or {@link #set(GhidraState, ScriptControls)} + * instead */ + @Deprecated(since = "11.5") public final void set(GhidraState state, TaskMonitor monitor, PrintWriter writer) { + set(state, new ScriptControls(writer, writer, monitor)); + } + + /** + * Set the context for this script. + * + * @param state the new state + */ + public final void set(GhidraState state) { this.state = state; - this.monitor = monitor; - this.writer = writer; loadVariablesFromState(); } + /** + * Set the state and controls for this script. + * + * @param state the new state + * @param controls new the controls + */ + public final void set(GhidraState state, ScriptControls controls) { + this.state = state; + loadVariablesFromState(); + + this.writer = controls.getWriter(); + this.errorWriter = controls.getErrorWriter(); + this.decorateOutput = controls.shouldDecorateOutput(); + this.monitor = controls.getMonitor(); + } + /** * Sets whether the user's previously selected values should be used when showing the various * {@code ask} methods. This is true by default, meaning that previous choices will be shown @@ -225,17 +257,51 @@ public abstract class GhidraScript extends FlatProgramAPI { /** * Execute/run script and {@link #doCleanup} afterwards. + *

+ * This method will use the given {@link PrintWriter} for both {@code stdout} and + * {@code stderr}. * * @param runState state object * @param runMonitor the monitor to use during run - * @param runWriter the target of script "print" statements + * @param runWriter the target of script "print" statements (may be null) + * @throws Exception if the script excepts + * @deprecated Use {@link #execute(GhidraState, ScriptControls)} instead to also set a + * {@link PrintWriter} for {@code stderr} + */ + @Deprecated(since = "11.5") + public final void execute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) + throws Exception { + execute(runState, new ScriptControls(runWriter, runWriter, runMonitor)); + } + + /** + * Execute/run script with the given {@link GhidraState state} and current + * {@link ScriptControls controls} and {@link #doCleanup} afterwards. + *

+ * NOTE: This method is not intended to be called by script writers. + * + * @param runState state object * @throws Exception if the script excepts */ - public final void execute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) + public final void execute(GhidraState runState) throws Exception { + execute(runState, new ScriptControls(writer, errorWriter, decorateOutput, monitor)); + } + + /** + * Execute/run script with the given {@link GhidraState state} and + * {@link ScriptControls controls} and {@link #doCleanup} afterwards. + *

+ * NOTE: This method is not intended to be called by script writers. + * + * @param runState state object + * @param runControls controls object + * @throws Exception if the script excepts + */ + public final void execute(GhidraState runState, ScriptControls runControls) throws Exception { boolean success = false; try { - doExecute(runState, runMonitor, runWriter); + doExecute(runState, runControls); success = true; } finally { @@ -243,11 +309,13 @@ public abstract class GhidraScript extends FlatProgramAPI { } } - private void doExecute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) + private void doExecute(GhidraState runState, ScriptControls runControls) throws Exception { this.state = runState; - this.monitor = runMonitor; - this.writer = runWriter; + this.writer = runControls.getWriter(); + this.errorWriter = runControls.getErrorWriter(); + this.decorateOutput = runControls.shouldDecorateOutput(); + this.monitor = runControls.getMonitor(); loadVariablesFromState(); loadPropertiesFile(); @@ -260,7 +328,8 @@ public abstract class GhidraScript extends FlatProgramAPI { executeNormal(); } else { - executeAsAnalysisWorker(scriptAnalysisMode == AnalysisMode.SUSPENDED, runMonitor); + executeAsAnalysisWorker(scriptAnalysisMode == AnalysisMode.SUSPENDED, + runControls.getMonitor()); } updateStateFromVariables(); } @@ -844,7 +913,7 @@ public abstract class GhidraScript extends FlatProgramAPI { "': unable to run this script type."); } - GhidraScript script = provider.getScriptInstance(scriptSource, writer); + GhidraScript script = provider.getScriptInstance(scriptSource, errorWriter); script.setScriptArgs(scriptArguments); if (potentialPropertiesFileLocs.size() > 0) { @@ -855,7 +924,7 @@ public abstract class GhidraScript extends FlatProgramAPI { updateStateFromVariables(); } - script.execute(scriptState, monitor, writer); + script.execute(scriptState); if (scriptState == state) { loadVariablesFromState(); @@ -949,74 +1018,44 @@ public abstract class GhidraScript extends FlatProgramAPI { } /** - * Prints a newline. - * - * @see #printf(String, Object...) + * Prints a newline to this script's {@code stdout} {@link PrintWriter}, which is set by + * {@link #set(GhidraState, ScriptControls)}. + *

+ * Additionally, the newline is written to Ghidra's log. */ public void println() { println(""); } /** - * Prints the message to the console followed by a line feed. + * Prints the {@link #decorateOutput optionally} {@link #decorate(String) decorated} message + * followed by a line feed to this script's {@code stdout} {@link PrintWriter}, which is set by + * {@link #set(GhidraState, ScriptControls)}. + *

+ * Additionally, the always {@link #decorate(String) decorated} message is written to Ghidra's + * log. * * @param message the message to print - * @see #printf(String, Object...) */ public void println(String message) { - String decoratedMessage = getScriptName() + "> " + message; + String decoratedMessage = decorate(message); - // note: use a Message object to facilitate script message log filtering Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage)); - if (isRunningHeadless()) { - return; - } - - PluginTool tool = state.getTool(); - if (tool == null) { - return; - } - - ConsoleService console = tool.getService(ConsoleService.class); - if (console == null) { - return; - } - - try { - console.addMessage(getScriptName(), message); - } - catch (Exception e) { - Msg.error(this, "Script Message: " + message, e); + if (writer != null) { + writer.println(decorateOutput ? decoratedMessage : message); } } /** - * A convenience method to print a formatted String using Java's printf - * feature, which is similar to that of the C programming language. - * For a full description on Java's - * printf usage, see {@link java.util.Formatter}. + * Prints the undecorated {@link java.util.Formatter formatted message} to this script's + * {@code stdout} {@link PrintWriter}, which is set by + * {@link #set(GhidraState, ScriptControls)}. *

- * For examples, see the included FormatExampleScript. - *

- * Note: This method will not: - *

- * If you would like the name of the script to precede you message, then you must add that - * yourself. The {@link #println(String)} does this via the following code: - *
-	 *     String messageWithSource = getScriptName() + "> " + message;
-	 * 
+ * Additionally, the undecorated formatted message is written to Ghidra's log. * * @param message the message to format - * @param args formatter arguments (see above) - * - * @see String#format(String, Object...) - * @see java.util.Formatter - * @see #print(String) - * @see #println(String) + * @param args C-like {@code printf} formatter arguments */ public void printf(String message, Object... args) { String formattedString = String.format(message, args); @@ -1024,20 +1063,12 @@ public abstract class GhidraScript extends FlatProgramAPI { } /** - * Prints the message to the console - no line feed + * Prints the undecorated message with no newline to this script's {@code stdout} + * {@link PrintWriter}, which is set by {@link #set(GhidraState, ScriptControls)}. *

- * Note: This method will not print out the name of the script, - * as does {@link #println(String)} - * - *

- * If you would like the name of the script to precede you message, then you must add that - * yourself. The {@link #println(String)} does this via the following code: - *

-	 *     String messageWithSource = getScriptName() + "> " + message;
-	 * 
+ * Additionally, the undecorated message is written to Ghidra's log. * * @param message the message to print - * @see #printf(String, Object...) */ public void print(String message) { // clients using print may add their own newline, which interferes with our logging, @@ -1051,57 +1082,41 @@ public abstract class GhidraScript extends FlatProgramAPI { } Msg.info(GhidraScript.class, new ScriptMessage(strippedMessage)); - if (isRunningHeadless()) { - return; - } - - PluginTool tool = state.getTool(); - if (tool == null) { - return; - } - - ConsoleService console = tool.getService(ConsoleService.class); - if (console == null) { - return; - } - - try { - console.print(message); - } - catch (Exception e) { - Msg.error(this, "Script Message: " + message, e); + if (writer != null) { + writer.print(message); } } /** - * Prints the error message to the console followed by a line feed. + * Prints the {@link #decorateOutput optionally} {@link #decorate(String) decorated} message + * followed by a line feed to this script's {@code stderr} {@link PrintWriter}, which is set by + * {@link #set(GhidraState, ScriptControls)}. + *

+ * Additionally, the always {@link #decorate(String) decorated} message is written to Ghidra's + * log as an error. * - * @param message the error message to print + * @param message the message to print */ public void printerr(String message) { - String msgMessage = getScriptName() + "> " + message; - Msg.error(GhidraScript.class, new ScriptMessage(msgMessage)); + String decoratedMessage = decorate(message); - if (isRunningHeadless()) { - return; - } + Msg.error(GhidraScript.class, new ScriptMessage(decoratedMessage)); - PluginTool tool = state.getTool(); - if (tool == null) { - return; + if (errorWriter != null) { + errorWriter.println(decorateOutput ? decoratedMessage : message); } + } - ConsoleService console = tool.getService(ConsoleService.class); - if (console == null) { - return; - } - - try { - console.addErrorMessage(getScriptName(), message); - } - catch (Exception e) { - Msg.error(this, "Script Message: " + message, e); - } + /** + * Decorates the given message, which is used by the {@link GhidraScript} "print" methods during + * logging and {@link #decorateOutput optionally} when outputting to the + * {@link PrintWriter}s. + * + * @param message The message to decorate + * @return The decorated message + */ + protected String decorate(String message) { + return getScriptName() + "> " + message; } /** @@ -3685,7 +3700,7 @@ public abstract class GhidraScript extends FlatProgramAPI { pm.openProgram(program); end(true); GhidraState newState = new GhidraState(tool, tool.getProject(), program, null, null, null); - set(newState, monitor, writer); + set(newState); start(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index 8d0a5cb1c9..8e29b39481 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -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. @@ -87,12 +87,12 @@ public abstract class GhidraScriptProvider * Returns a GhidraScript instance for the specified source file. * * @param sourceFile the source file - * @param writer the print writer to write warning/error messages. If the error prevents + * @param errorWriter the print writer to write warning/error messages. If the error prevents * success, throw an exception instead. The caller will print the error. * @return a GhidraScript instance for the specified source file * @throws GhidraScriptLoadException when the script instance cannot be created */ - public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) + public abstract GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter errorWriter) throws GhidraScriptLoadException; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java new file mode 100644 index 0000000000..a0be99ca14 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java @@ -0,0 +1,133 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.script; + +import java.io.OutputStream; +import java.io.PrintWriter; + +import ghidra.app.plugin.core.interpreter.InterpreterConsole; +import ghidra.app.services.ConsoleService; +import ghidra.util.task.TaskMonitor; + +/** + * Class to encapsulate {@link GhidraScript} control mechanisms such as stdout/stderr writers and + * other feedback to the user + */ +public class ScriptControls { + + /** + * A {@link ScriptControls} that does nothing + */ + public static final ScriptControls NONE = + new ScriptControls(null, null, false, TaskMonitor.DUMMY); + + private PrintWriter writer; + private PrintWriter errorWriter; + private boolean decorateOutput; + private TaskMonitor monitor; + + /** + * Creates a new {@link ScriptControls} + * + * @param writer The target of script "print" statements (may be null) + * @param errorWriter The target of script "printerr" statements (may be null) + * @param decorateOutput True to decorate the writer output with a script name prefix; + * otherwise, false (see {@link GhidraScript#decorate(String)} + * @param monitor A cancellable monitor + */ + public ScriptControls(PrintWriter writer, PrintWriter errorWriter, boolean decorateOutput, + TaskMonitor monitor) { + this.writer = writer; + this.errorWriter = errorWriter; + this.decorateOutput = decorateOutput; + this.monitor = monitor; + } + + /** + * Creates a new {@link ScriptControls} with no decorated output + * + * @param writer The target of script "print" statements (may be null) + * @param errorWriter The target of script "printerr" statements (may be null) + * otherwise, false (see {@link GhidraScript#decorate(String)} + * @param monitor A cancellable monitor + */ + public ScriptControls(PrintWriter writer, PrintWriter errorWriter, TaskMonitor monitor) { + this(writer, errorWriter, false, monitor); + } + + /** + * Creates a new {@link ScriptControls} with no decorated output + * + * @param stream The target of script "print" statements (may be null) + * @param errorStream The target of script "printerr" statements (may be null) + * otherwise, false (see {@link GhidraScript#decorate(String)} + * @param monitor A cancellable monitor + */ + public ScriptControls(OutputStream stream, OutputStream errorStream, TaskMonitor monitor) { + this(new PrintWriter(stream, true), new PrintWriter(errorStream, true), false, monitor); + } + + /** + * Creates a new {@link ScriptControls} with no decorated output + * + * @param console The target of script "print" and "printerr" statements + * @param monitor A cancellable monitor + */ + public ScriptControls(ConsoleService console, TaskMonitor monitor) { + this(console.getStdOut(), console.getStdErr(), monitor); + } + + /** + * Creates a new {@link ScriptControls} with no decorated output + * + * @param console The target of script "print" and "printerr" statements + * @param monitor A cancellable monitor + */ + public ScriptControls(InterpreterConsole console, TaskMonitor monitor) { + this(console.getStdOut(), console.getStdErr(), monitor); + } + + /** + * {@return the target of script "print" statements (may be null)} + */ + public PrintWriter getWriter() { + return writer; + } + + /** + * {@return the target of script "printerr" statements (may be null)} + */ + public PrintWriter getErrorWriter() { + return errorWriter; + } + + /** + * {@return True to decorate the writer output with a script name prefix; otherwise, false} + * + * @see GhidraScript#decorate(String) + */ + public boolean shouldDecorateOutput() { + return decorateOutput; + } + + /** + * {@return A cancellable monitor} + */ + public TaskMonitor getMonitor() { + return monitor; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java index 5ee27678a7..868ff9d141 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java @@ -77,10 +77,9 @@ public class GhidraScriptRunner implements GhidraLaunchable { srcFile != null ? srcFile.getAbsolutePath() : (script.getClass().getName() + ".class"); try { - PrintWriter writer = new PrintWriter(System.out); Msg.info(this, "SCRIPT: " + scriptName); - script.execute(scriptState, TaskMonitor.DUMMY, writer); - writer.flush(); + ScriptControls controls = new ScriptControls(System.out, System.err, TaskMonitor.DUMMY); + script.execute(scriptState, controls); } catch (Exception exc) { Program prog = scriptState.getCurrentProgram(); @@ -107,8 +106,8 @@ public class GhidraScriptRunner implements GhidraLaunchable { "ensure you have installed the necessary plugin."); } - PrintWriter writer = new PrintWriter(System.out); - GhidraScript foundScript = provider.getScriptInstance(scriptSourceFile, writer); + PrintWriter errWriter = new PrintWriter(System.err); + GhidraScript foundScript = provider.getScriptInstance(scriptSourceFile, errWriter); if (propertiesFilePath != null) { // Get basename, assume that it ends in .java, since we've already covered the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index d1d69c923f..b9d73e9091 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -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. @@ -583,10 +583,11 @@ public class HeadlessAnalyzer { srcFile != null ? srcFile.getAbsolutePath() : (script.getClass().getName() + ".class"); try { - PrintWriter writer = new PrintWriter(System.out); Msg.info(this, "SCRIPT: " + scriptName); - script.execute(scriptState, TaskMonitor.DUMMY, writer); - writer.flush(); + + // Execute the script, but don't directly write to stdout or stderr. The headless + // analyzer only uses the logging mechanism to get output to the user. + script.execute(scriptState, ScriptControls.NONE); } catch (Exception exc) { String logErrorMsg = "REPORT SCRIPT ERROR: "; @@ -908,8 +909,8 @@ public class HeadlessAnalyzer { // GhidraScriptProvider case GhidraScriptProvider provider = GhidraScriptUtil.getProvider(currScriptFile); - PrintWriter writer = new PrintWriter(System.out); - currScript = provider.getScriptInstance(currScriptFile, writer); + PrintWriter errWriter = new PrintWriter(System.err); + currScript = provider.getScriptInstance(currScriptFile, errWriter); currScript.setScriptArgs(scriptArgs); if (options.propertiesFilePaths.size() > 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessScript.java index b991d6212f..35b32d85c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessScript.java @@ -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. @@ -405,7 +405,7 @@ public abstract class HeadlessScript extends GhidraScript { "': unable to run this script type."); } - GhidraScript script = provider.getScriptInstance(scriptSource, writer); + GhidraScript script = provider.getScriptInstance(scriptSource, errorWriter); isHeadlessScript = script instanceof HeadlessScript ? true : false; if (potentialPropertiesFileLocs.size() > 0) { @@ -423,7 +423,7 @@ public abstract class HeadlessScript extends GhidraScript { script.setScriptArgs(scriptArguments); - script.execute(scriptState, monitor, writer); + script.execute(scriptState); if (scriptState == state) { loadVariablesFromState(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index 7f9ccfad62..e23d218a9b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -557,18 +557,18 @@ public class TestEnv { } JavaScriptProvider scriptProvider = new JavaScriptProvider(); - PrintWriter writer = new PrintWriter(System.out); + PrintWriter errWriter = new PrintWriter(System.err); ResourceFile resourceFile = new ResourceFile(scriptFile); GhidraScript script = null; try { - script = scriptProvider.getScriptInstance(resourceFile, writer); + script = scriptProvider.getScriptInstance(resourceFile, errWriter); } catch (GhidraScriptLoadException e) { Msg.error(TestEnv.class, "Problem creating script", e); } if (script == null) { - writer.flush(); + errWriter.flush(); throw new RuntimeException("Failed to compile script " + scriptFile.getAbsolutePath()); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index b425febf24..e5a99e216b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -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. @@ -1610,16 +1610,14 @@ public abstract class AbstractGhidraScriptMgrPluginTest } protected class SpyConsole extends ConsoleComponentProvider { - protected StringBuffer apiBuffer; protected StringWriter outBuffer = new StringWriter(); protected StringWriter errBuffer = new StringWriter(); - protected PrintWriter out = new PrintWriter(outBuffer); - protected PrintWriter err = new PrintWriter(errBuffer); + protected PrintWriter out = new PrintWriter(outBuffer, true); + protected PrintWriter err = new PrintWriter(errBuffer, true); SpyConsole() { super(plugin.getTool(), "Spy Console"); - this.apiBuffer = new StringBuffer(); } @Override @@ -1633,25 +1631,24 @@ public abstract class AbstractGhidraScriptMgrPluginTest } void clear() { - apiBuffer = new StringBuffer(); outBuffer = new StringWriter(); errBuffer = new StringWriter(); } @Override public void println(String msg) { - apiBuffer.append(msg).append('\n'); + out.println(msg); Msg.trace(this, "Spy Script Console - println(): " + msg); } @Override public void addMessage(String originator, String msg) { - apiBuffer.append(msg).append('\n'); + out.println(msg); Msg.trace(this, "Spy Script Console - addMessage(): " + msg); } String getApiOutput() { - return apiBuffer.toString(); + return outBuffer.toString(); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java index ff00dfc5ea..0833a849bf 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/BundleStatusManagerTest.java @@ -118,7 +118,7 @@ public class BundleStatusManagerTest extends AbstractGhidraScriptMgrPluginTest { //@formatter:off final String EXPECTED_OUTPUT = TEST_SCRIPT_NAME+".java> Running...\n" + - TEST_SCRIPT_NAME+".java> Hello from pack2.Klass2\n" + + "Hello from pack2.Klass2\n" + TEST_SCRIPT_NAME+".java> Finished!\n"; //@formatter:on final File dir1 = new File(getTestDirectoryPath() + "/test_scripts1"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java index 6dfda2a23d..f1d9b9a1f4 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java @@ -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. @@ -54,7 +54,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes String consoleText = getConsoleText(); assertTrue("ConsoleText was \"" + consoleText + "\".", - consoleText.indexOf("> Hello World") >= 0); + consoleText.indexOf("Hello World") >= 0); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java index 7dfd5322ba..6ac044ec73 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java @@ -100,7 +100,7 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes String scriptOutput = runSelectedScript(script.getName()); assertTrue("Script output not generated", - scriptOutput.contains("> new scripts are neato!")); + scriptOutput.contains("new scripts are neato!")); assertFalse("Script output has value from previous test run - did script not get deleted?", scriptOutput.contains("Value == 3368601")); @@ -135,7 +135,7 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes String updatedScriptOutput = runSelectedScript(script.getName()); assertTrue("Script output not updated with new script contents - did recompile work?", - StringUtilities.containsAll(updatedScriptOutput, "> new scripts are neato!", + StringUtilities.containsAll(updatedScriptOutput, "new scripts are neato!", "Value == 3368601")); deleteScriptThroughUI(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java index c56fcd6dfc..31f7a34284 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java @@ -42,7 +42,6 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; -import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationTest { @@ -902,7 +901,7 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT // test stub } }; - script.set(state, TaskMonitor.DUMMY, null); + script.set(state, ScriptControls.NONE); URL url = GhidraScriptTest.class.getResource("GhidraScriptAsk.properties"); assertNotNull("Test cannot run without properties file!", url); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptRealProgramTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptRealProgramTest.java index 2365bbab53..8c599d83c1 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptRealProgramTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptRealProgramTest.java @@ -31,7 +31,6 @@ import ghidra.program.model.scalar.Scalar; import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.test.*; -import ghidra.util.task.TaskMonitor; public class GhidraScriptRealProgramTest extends AbstractGhidraHeadedIntegrationTest { @@ -399,7 +398,7 @@ public class GhidraScriptRealProgramTest extends AbstractGhidraHeadedIntegration // test stub } }; - script.set(state, TaskMonitor.DUMMY, null); + script.set(state, ScriptControls.NONE); return script; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptTest.java index c8141cad34..09c9ef7205 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptTest.java @@ -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. @@ -44,7 +44,6 @@ import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.Msg; import ghidra.util.exception.AssertException; -import ghidra.util.task.TaskMonitor; public class GhidraScriptTest extends AbstractGhidraHeadedIntegrationTest { @@ -773,7 +772,7 @@ public class GhidraScriptTest extends AbstractGhidraHeadedIntegrationTest { // test stub } }; - script.set(state, TaskMonitor.DUMMY, null); + script.set(state, ScriptControls.NONE); return script; } diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/ShowConstantUse.java b/Ghidra/Features/Decompiler/ghidra_scripts/ShowConstantUse.java index a02dbd6ab3..82023094c6 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/ShowConstantUse.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/ShowConstantUse.java @@ -329,8 +329,8 @@ public class ShowConstantUse extends GhidraScript { ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(name); if (scriptSource != null) { GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - GhidraScript script = provider.getScriptInstance(scriptSource, writer); - script.execute(scriptState, monitor, writer); + GhidraScript script = provider.getScriptInstance(scriptSource, errorWriter); + script.execute(scriptState); return; } } diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java index 62f90f1ad7..40ab3ff10d 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java @@ -20,8 +20,7 @@ import java.nio.file.Path; import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooserMode; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraState; +import ghidra.app.script.*; import ghidra.app.services.ConsoleService; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; @@ -72,7 +71,8 @@ public class RunPCodeExportScriptTask extends Task { script.setScriptArgs(new String[] { facts_directory }); console.addMessage(scriptName, "Running..."); - script.execute(currentState, monitor, console.getStdOut()); + script.execute(currentState, + new ScriptControls(console.getStdOut(), console.getStdErr(), false, monitor)); console.addMessage(scriptName, "Finished!"); } catch (CancelledException e) { diff --git a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPlugin.java b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPlugin.java index a683ca5132..263cf88b15 100644 --- a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPlugin.java +++ b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPlugin.java @@ -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. @@ -34,6 +34,7 @@ import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.console.CodeCompletion; import ghidra.app.plugin.core.interpreter.*; import ghidra.app.script.GhidraState; +import ghidra.app.script.ScriptControls; import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginInfo; @@ -232,7 +233,7 @@ public class JythonPlugin extends ProgramPlugin interactiveScript.set( new GhidraState(tool, tool.getProject(), getCurrentProgram(), getProgramLocation(), getProgramSelection(), getProgramHighlight()), - interactiveTaskMonitor, new PrintWriter(getConsole().getStdOut())); + new ScriptControls(console, interactiveTaskMonitor)); interpreter.injectScriptHierarchy(interactiveScript); interactiveTaskMonitor = new JythonInteractiveTaskMonitor(console.getStdOut()); @@ -288,7 +289,7 @@ public class JythonPlugin extends ProgramPlugin interactiveScript.set( new GhidraState(tool, tool.getProject(), currentProgram, currentLocation, currentSelection, currentHighlight), - interactiveTaskMonitor, console.getOutWriter()); + new ScriptControls(console, interactiveTaskMonitor)); return interpreter.getCommandCompletions(cmd, includeBuiltins, caretPos); } @@ -377,7 +378,7 @@ public class JythonPlugin extends ProgramPlugin } public JythonInteractiveTaskMonitor(OutputStream stdout) { - this(new PrintWriter(stdout)); + this(new PrintWriter(stdout, true)); } @Override diff --git a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPluginExecutionThread.java b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPluginExecutionThread.java index 66d8981281..46d8a18821 100644 --- a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPluginExecutionThread.java +++ b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonPluginExecutionThread.java @@ -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. @@ -16,14 +16,15 @@ package ghidra.jython; import java.io.File; -import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; import org.python.core.PyException; import db.Transaction; import generic.jar.ResourceFile; +import ghidra.app.plugin.core.interpreter.InterpreterConsole; import ghidra.app.script.GhidraState; +import ghidra.app.script.ScriptControls; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Program; import ghidra.util.task.TaskMonitor; @@ -59,6 +60,7 @@ class JythonPluginExecutionThread extends Thread { TaskMonitor interactiveTaskMonitor = plugin.getInteractiveTaskMonitor(); JythonScript interactiveScript = plugin.getInteractiveScript(); Program program = plugin.getCurrentProgram(); + InterpreterConsole console = plugin.getConsole(); // Setup transaction for the execution. try (Transaction tx = program != null ? program.openTransaction("Jython command") : null) { @@ -69,7 +71,7 @@ class JythonPluginExecutionThread extends Thread { interactiveScript.set( new GhidraState(tool, tool.getProject(), program, plugin.getProgramLocation(), plugin.getProgramSelection(), plugin.getProgramHighlight()), - interactiveTaskMonitor, new PrintWriter(plugin.getConsole().getStdOut())); + new ScriptControls(console, interactiveTaskMonitor)); // Execute the command moreInputWanted.set(false); @@ -81,14 +83,13 @@ class JythonPluginExecutionThread extends Thread { plugin.reset(); } else { - plugin.getConsole() - .getErrWriter() + console.getErrWriter() .println( "Suppressing exception: " + PyException.exceptionClassName(pye.type)); } } catch (StackOverflowError soe) { - plugin.getConsole().getErrWriter().println("Stack overflow!"); + console.getErrWriter().println("Stack overflow!"); } finally { interactiveScript.end(false); // end any transactions the script may have started diff --git a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScript.java b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScript.java index 5be412cf84..fbf7f1e99d 100644 --- a/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScript.java +++ b/Ghidra/Features/Jython/src/main/java/ghidra/jython/JythonScript.java @@ -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. @@ -62,7 +62,7 @@ public class JythonScript extends GhidraScript { ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName); if (scriptSource != null) { GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource); - GhidraScript ghidraScript = provider.getScriptInstance(scriptSource, writer); + GhidraScript ghidraScript = provider.getScriptInstance(scriptSource, errorWriter); if (ghidraScript == null) { throw new IllegalArgumentException("Script does not exist: " + scriptName); } @@ -72,12 +72,12 @@ public class JythonScript extends GhidraScript { } if (ghidraScript instanceof JythonScript) { - ghidraScript.set(scriptState, monitor, writer); + ghidraScript.set(scriptState); JythonScript jythonScript = (JythonScript) ghidraScript; interpreter.execFile(jythonScript.getSourceFile(), jythonScript); } else { - ghidraScript.execute(scriptState, monitor, writer); + ghidraScript.execute(scriptState); } if (scriptState == state) { diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java index 9287776f64..d7ad95ffb7 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java @@ -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. @@ -25,8 +25,7 @@ import org.junit.*; import generic.jar.ResourceFile; import ghidra.app.plugin.core.console.ConsolePlugin; import ghidra.app.plugin.core.osgi.BundleHost; -import ghidra.app.script.GhidraScriptUtil; -import ghidra.app.script.GhidraState; +import ghidra.app.script.*; import ghidra.app.services.ConsoleService; import ghidra.framework.Application; import ghidra.framework.plugintool.PluginTool; @@ -128,7 +127,7 @@ public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest { JythonScriptProvider scriptProvider = new JythonScriptProvider(); PrintWriter writer = new PrintWriter(new ByteArrayOutputStream()); JythonScript script = (JythonScript) scriptProvider.getScriptInstance(scriptFile, writer); - script.set(state, TaskMonitor.DUMMY, writer); + script.set(state, new ScriptControls(writer, writer, TaskMonitor.DUMMY)); script.run(); waitForSwing(); diff --git a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/WindowsResourceReferenceAnalyzer.java b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/WindowsResourceReferenceAnalyzer.java index 1aa148b046..d671bfb847 100644 --- a/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/WindowsResourceReferenceAnalyzer.java +++ b/Ghidra/Features/MicrosoftCodeAnalyzer/src/main/java/ghidra/app/plugin/prototype/MicrosoftCodeAnalyzerPlugin/WindowsResourceReferenceAnalyzer.java @@ -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. @@ -93,9 +93,10 @@ public class WindowsResourceReferenceAnalyzer extends AbstractAnalyzer { } PrintWriter writer = getOutputMsgStream(tool); + PrintWriter errWriter = getErrorMsgStream(tool); - GhidraScript script = provider.getScriptInstance(sourceFile, writer); - script.set(state, monitor, writer); + GhidraScript script = provider.getScriptInstance(sourceFile, errWriter); + script.set(state, new ScriptControls(writer, errWriter, monitor)); // This code was added so the analyzer won't print script messages to console // This also adds the ability to pass the option to add or not add bookmarks to the script @@ -141,6 +142,16 @@ public class WindowsResourceReferenceAnalyzer extends AbstractAnalyzer { return new PrintWriter(System.out); } + private PrintWriter getErrorMsgStream(PluginTool tool) { + if (tool != null) { + ConsoleService console = tool.getService(ConsoleService.class); + if (console != null) { + return console.getStdErr(); + } + } + return new PrintWriter(System.err); + } + @Override public boolean removed(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { diff --git a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraPlugin.java b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraPlugin.java index e819341c0b..66cac39a8a 100644 --- a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraPlugin.java +++ b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraPlugin.java @@ -22,6 +22,7 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.interpreter.InterpreterPanelService; import ghidra.app.script.GhidraState; +import ghidra.app.script.ScriptControls; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -57,7 +58,7 @@ public class PyGhidraPlugin extends ProgramPlugin { super(tool); GhidraState state = new GhidraState(tool, tool.getProject(), null, null, null, null); // use the copy constructor so this state doesn't fire plugin events - script.set(new GhidraState(state), null, null); + script.set(new GhidraState(state), ScriptControls.NONE); } /** diff --git a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraScriptProvider.java b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraScriptProvider.java index a60feb90a3..60e0ee18c1 100644 --- a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraScriptProvider.java +++ b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/PyGhidraScriptProvider.java @@ -86,13 +86,13 @@ public final class PyGhidraScriptProvider extends AbstractPythonScriptProvider { "currentAddress", "currentLocation", "currentSelection", "currentHighlight", "currentProgram", "monitor", "potentialPropertiesFileLocs", "propertiesFileParams", - "sourceFile", "state", "writer" + "sourceFile", "state", "writer", "errorWriter" }, types = { Address.class, ProgramLocation.class, ProgramSelection.class, ProgramSelection.class, Program.class, TaskMonitor.class, List.class, GhidraScriptProperties.class, - ResourceFile.class, GhidraState.class, PrintWriter.class + ResourceFile.class, GhidraState.class, PrintWriter.class, PrintWriter.class } ) final static class PyGhidraGhidraScript extends GhidraScript @@ -120,13 +120,13 @@ public final class PyGhidraScriptProvider extends AbstractPythonScriptProvider { "currentAddress", "currentLocation", "currentSelection", "currentHighlight", "currentProgram", "monitor", "potentialPropertiesFileLocs", "propertiesFileParams", - "sourceFile", "state", "writer" + "sourceFile", "state", "writer", "errorWriter" }, types = { Address.class, ProgramLocation.class, ProgramSelection.class, ProgramSelection.class, Program.class, TaskMonitor.class, List.class, GhidraScriptProperties.class, - ResourceFile.class, GhidraState.class, PrintWriter.class + ResourceFile.class, GhidraState.class, PrintWriter.class, PrintWriter.class } ) final static class PyGhidraHeadlessScript extends HeadlessScript diff --git a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/interpreter/InterpreterGhidraScript.java b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/interpreter/InterpreterGhidraScript.java index ad91e7056a..a1961aaf95 100644 --- a/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/interpreter/InterpreterGhidraScript.java +++ b/Ghidra/Features/PyGhidra/src/main/java/ghidra/pyghidra/interpreter/InterpreterGhidraScript.java @@ -17,8 +17,7 @@ package ghidra.pyghidra.interpreter; import java.io.PrintWriter; -import ghidra.app.script.GhidraScript; -import ghidra.app.script.GhidraState; +import ghidra.app.script.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; @@ -86,6 +85,6 @@ public final class InterpreterGhidraScript extends GhidraScript { } public void set(GhidraState state, PrintWriter writer) { - set(state, new InterpreterTaskMonitor(writer), writer); + set(state, new ScriptControls(writer, writer, new InterpreterTaskMonitor(writer))); } }