Merge remote-tracking branch 'origin/GP-5737_ryanmkurtz_script-output--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-06-09 12:20:46 -04:00
commit c224c5d44e
26 changed files with 359 additions and 206 deletions

View file

@ -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) {

View file

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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)}.
* <p>
* 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)}.
* <p>
* 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 <code>printf</code>
* feature, which is similar to that of the C programming language.
* For a full description on Java's
* <code>printf</code> 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)}.
* <p>
* For examples, see the included <code>FormatExampleScript</code>.
* <p>
* <b><u>Note:</u> This method will not:</b>
* <ul>
* <li><b>print out the name of the script, as does {@link #println(String)}</b></li>
* <li><b>print a newline</b></li>
* </ul>
* 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:
* <pre>
* String messageWithSource = getScriptName() + "&gt; " + message;
* </pre>
* 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)}.
* <p>
* <b><u>Note:</u> This method will not print out the name of the script,
* as does {@link #println(String)}
* </b>
* <p>
* 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:
* <pre>
* String messageWithSource = getScriptName() + "&gt; " + message;
* </pre>
* 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)}.
* <p>
* 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();
}

View file

@ -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;
/**

View file

@ -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;
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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();

View file

@ -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());
}

View file

@ -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();
}
}

View file

@ -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");

View file

@ -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);
}

View file

@ -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();

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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();

View file

@ -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 {

View file

@ -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);
}
/**

View file

@ -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

View file

@ -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)));
}
}