mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Candidate release of source code.
This commit is contained in:
parent
db81e6b3b0
commit
79d8f164f8
12449 changed files with 2800756 additions and 16 deletions
9
Ghidra/Features/Python/src/main/help/help/TOC_Source.xml
Normal file
9
Ghidra/Features/Python/src/main/help/help/TOC_Source.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' encoding='ISO-8859-1' ?>
|
||||
|
||||
<tocroot>
|
||||
<tocref id="Ghidra Functionality">
|
||||
<tocref id="Scripting">
|
||||
<tocdef id="Python Interpreter" sortgroup="f" text="Python Interpreter" target="help/topics/Python/interpreter.html" />
|
||||
</tocref>
|
||||
</tocref>
|
||||
</tocroot>
|
|
@ -0,0 +1,58 @@
|
|||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
WARNING!
|
||||
This file is copied to all help directories. If you change this file, you must copy it
|
||||
to each src/main/help/help/shared directory.
|
||||
|
||||
|
||||
Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
|
||||
px (pixel) or with no type marking.
|
||||
|
||||
*/
|
||||
|
||||
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
|
||||
li { font-family:times new roman; font-size:14pt; }
|
||||
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
|
||||
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
|
||||
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; font-size:14pt; font-weight:bold; }
|
||||
h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
|
||||
|
||||
/*
|
||||
P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
|
||||
way it had been done in the beginning). The net effect is that the text is indented. In
|
||||
modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
|
||||
blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
|
||||
that the 'blockquote p' definition will inherit from the first 'p' definition.
|
||||
*/
|
||||
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
|
||||
blockquote p { margin-left: 10px; }
|
||||
|
||||
p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
|
||||
p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
|
||||
p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
|
||||
p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
|
||||
|
||||
/*
|
||||
We wish for a tables to have space between it and the preceding element, so that text
|
||||
is not too close to the top of the table. Also, nest the table a bit so that it is clear
|
||||
the table relates to the preceding text.
|
||||
*/
|
||||
table { margin-left: 20px; margin-top: 10px; width: 80%;}
|
||||
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
|
||||
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
|
||||
|
||||
code { color: black; font-family: courier new; font-size: 14pt; }
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,157 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Python Interpreter</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Python"></A>Python Interpreter</H1>
|
||||
|
||||
<P>
|
||||
The Ghidra <I>Python Interpreter</I> provides a full general-purpose Python interactive shell
|
||||
and allows you to interact with your current Ghidra session by exposing Ghidra's powerful Java
|
||||
API through the magic of Jython.
|
||||
</P>
|
||||
|
||||
<H2>Environment</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Python Interpreter</I> is configured to run in a similar context as a Ghidra
|
||||
script. Therefore, you immediately have access to variables such as <TT>currentProgram</TT>,
|
||||
<TT>currentSelection</TT>, <TT>currentAddress</TT>, etc without needing to import them.
|
||||
These variables exist as Java objects behind the scenes, but Jython allows you to interact with
|
||||
them through a Python interface, which is similar to Java in some ways.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
As in Java, classes outside of your current package/module need to be explicitly imported.
|
||||
For example, consider the following code snippet:
|
||||
</P>
|
||||
<BR>
|
||||
|
||||
<PRE>
|
||||
<FONT COLOR="GREEN"># Get a data type from the user</FONT>
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType != None: print "Chosen data type: " + str(dataType)
|
||||
</PRE>
|
||||
|
||||
<P>
|
||||
<TT>currentProgram</TT> and <TT>state</TT> are defined within the Ghidra scripting class
|
||||
hierarchy, so nothing has to be explicitly imported before they can be used. However, because
|
||||
the <TT>DataTypeSelectionDialog</TT> class and <TT>AllowedDataType</TT> enum reside in
|
||||
different packages, they must be explicitly imported. Failure to do so will result in a
|
||||
Python <TT><FONT COLOR="RED">NameError</FONT></TT>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Keybindings</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Python Interpreter</I> supports the following keybindings:
|
||||
<UL>
|
||||
<LI><B>CTRL+D:</B> Clear the console and reset the interpreter</LI>
|
||||
<LI><B>CTRL+I:</B> Interrupt the interpreter</LI>
|
||||
<LI><B>(up):</B> Move backward in command stack</LI>
|
||||
<LI><B>(down):</B> Move forward in command stack</LI>
|
||||
<LI><B>TAB:</B> Show code completion window</LI>
|
||||
</UL>
|
||||
|
||||
<P>
|
||||
With the code completion window open:
|
||||
<UL>
|
||||
<LI><B>TAB:</B> Insert currently-selected code completion (if no completion selected, select the first available)</LI>
|
||||
<LI><B>ENTER:</B> Insert selected completion (if any) and close the completion window</LI>
|
||||
<LI><B>(up):</B> Select previous code completion</LI>
|
||||
<LI><B>(down):</B> Select next code completion</LI>
|
||||
<LI><B>ESC:</B> Hide code completion window</LI>
|
||||
</UL>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Copy/Paste</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
Copy and paste from within the Ghidra <I>Python Interpreter</I> should work as expected for
|
||||
your given environment:
|
||||
<UL>
|
||||
<LI><B>Windows:</B> CTRL+C / CTRL+V</LI>
|
||||
<LI><B>Linux:</B> CTRL+C / CTRL+V</LI>
|
||||
<LI><B>OS X:</B> COMMAND+C / COMMAND+V</LI>
|
||||
</UL>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>API Documentation</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The built-in <TT>help()</TT> Python function has been altered by the Ghidra <I>Python Interpreter</I>
|
||||
to add support for displaying Ghidra's Javadoc (where available) for a given Ghidra class, method,
|
||||
or variable. For example, to see Ghidra's Javadoc on the <TT>state</TT> variable, simply do:
|
||||
<PRE>
|
||||
>>> help(state)
|
||||
#####################################################
|
||||
class ghidra.app.script.GhidraState
|
||||
extends java.lang.Object
|
||||
|
||||
Represents the current state of a Ghidra tool
|
||||
|
||||
#####################################################
|
||||
|
||||
PluginTool getTool()
|
||||
Returns the current tool.
|
||||
|
||||
@return ghidra.framework.plugintool.PluginTool: the current tool
|
||||
|
||||
-----------------------------------------------------
|
||||
Project getProject()
|
||||
Returns the current project.
|
||||
|
||||
@return ghidra.framework.model.Project: the current project
|
||||
|
||||
-----------------------------------------------------
|
||||
...
|
||||
...
|
||||
...
|
||||
</PRE>
|
||||
<P>
|
||||
Calling help() with no arguments will show the Javadoc for the GhidraScript class.
|
||||
</P>
|
||||
<P>
|
||||
<B>Note:</B> It may be necessary to import a Ghidra class before calling the built-in <TT>help()</TT>
|
||||
Python function on it. Failure to do so will result in a Python <TT><FONT COLOR="RED">NameError</FONT></TT>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Additional Help</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
For more information on the Jython environment, such as how to interact with Java objects
|
||||
through a Python interface, please refer to Jython's free e-book which can be found on the
|
||||
Internet at <I><B>www.jython.org/jythonbook/en/1.0/</B></I>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Clear_Interpreter"></A>Clear <IMG border="0" src="images/erase16.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command clears the interpreter's display. Its effect is purely visual.
|
||||
It does not affect the state of the interpreter in any way.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="left" class="providedbyplugin">Provided by: <I>PythonPlugin</I></P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
</BODY>
|
||||
</HTML>
|
|
@ -0,0 +1,530 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
|
||||
import org.python.core.*;
|
||||
import org.python.util.InteractiveInterpreter;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A python interpreter meant for Ghidra's use. Each interpreter you get will have its own
|
||||
* variable space so they should not interfere with each other.
|
||||
* <p>
|
||||
* There is no longer a way to reset an interpreter...it was too complicated to get right.
|
||||
* Instead, you should {@link #cleanup()} your old interpreter and make a new one.
|
||||
*/
|
||||
public class GhidraPythonInterpreter extends InteractiveInterpreter {
|
||||
|
||||
private static boolean pythonInitialized;
|
||||
private static List<PyString> defaultPythonPath;
|
||||
|
||||
private TraceFunction interruptTraceFunction;
|
||||
private PyModule introspectModule;
|
||||
private PyModule builtinModule;
|
||||
private PyObject interrupt;
|
||||
private boolean scriptMethodsInjected;
|
||||
private boolean cleanedUp;
|
||||
|
||||
/**
|
||||
* Gets a new GhidraPythonInterpreter instance.
|
||||
*
|
||||
* @return A new GhidraPythonInterpreter. Could be null if it failed to be created.
|
||||
*/
|
||||
public static GhidraPythonInterpreter get() {
|
||||
|
||||
// Initialize the python environment if necessary. Only needs to happen once.
|
||||
if (!pythonInitialized) {
|
||||
try {
|
||||
// Setup python home directory
|
||||
PythonUtils.setupPythonHomeDir();
|
||||
|
||||
// Setup python cache directory
|
||||
PythonUtils.setupPythonCacheDir(TaskMonitor.DUMMY);
|
||||
|
||||
// Indicate that we've initialized the python environment, which should
|
||||
// only happen once.
|
||||
pythonInitialized = true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(GhidraPythonInterpreter.class, null, "Python error",
|
||||
"Problem getting Ghirda Python interpreter", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up our default system state, including prompt styles.
|
||||
PySystemState state = new PySystemState();
|
||||
state.ps1 = new PyString(">>> ");
|
||||
state.ps2 = new PyString("... ");
|
||||
|
||||
// Return a new instance of our interpreter
|
||||
return new GhidraPythonInterpreter(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra python interpreter object.
|
||||
*
|
||||
* @param state The initial system state of the interpreter.
|
||||
*/
|
||||
private GhidraPythonInterpreter(PySystemState state) {
|
||||
super(null, state);
|
||||
|
||||
// Store the default python path in case we need to reset it later.
|
||||
defaultPythonPath = new ArrayList<>();
|
||||
for (Object object : systemState.path) {
|
||||
defaultPythonPath.add(Py.newString(object.toString()));
|
||||
}
|
||||
|
||||
// Allow interruption of python code to occur when various code paths are
|
||||
// encountered.
|
||||
interruptTraceFunction = new InterruptTraceFunction();
|
||||
|
||||
// Setup __main__ module
|
||||
PyModule mod = imp.addModule("__main__");
|
||||
setLocals(mod.__dict__);
|
||||
|
||||
// Load site.py (standard Python practice).
|
||||
// This will also load our sitecustomize.py module.
|
||||
imp.load("site");
|
||||
|
||||
// Setup code completion module.
|
||||
// Note that this is not exported to the global address space by default.
|
||||
introspectModule = (PyModule) imp.load("jintrospect");
|
||||
|
||||
// Add __builtin__ module for code completion
|
||||
builtinModule = (PyModule) imp.load("__builtin__");
|
||||
|
||||
initializePythonPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes/resets the python path to include all known Ghidra script paths.
|
||||
*/
|
||||
private void initializePythonPath() {
|
||||
|
||||
// Restore the python path back to default.
|
||||
systemState.path.retainAll(defaultPythonPath);
|
||||
|
||||
// Add in Ghidra script source directories
|
||||
for (ResourceFile resourceFile : GhidraScriptUtil.getScriptSourceDirectories()) {
|
||||
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
|
||||
}
|
||||
|
||||
for (ResourceFile resourceFile : GhidraScriptUtil.getScriptBinDirectories()) {
|
||||
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
|
||||
}
|
||||
|
||||
// Add in the PyDev remote debugger module
|
||||
if (!SystemUtilities.isInDevelopmentMode()) {
|
||||
File pyDevSrcDir = PyDevUtils.getPyDevSrcDir();
|
||||
if (pyDevSrcDir != null) {
|
||||
systemState.path.append(Py.newString(pyDevSrcDir.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes (executes) a line of Python to the interpreter.
|
||||
*
|
||||
* @param line the line of Python to push to the interpreter
|
||||
* @param script a PythonScript from which we load state (or null)
|
||||
* @return true if more input is needed before execution can occur
|
||||
* @throws PyException if an unhandled exception occurred while executing the line of python
|
||||
* @throws IllegalStateException if this interpreter has been cleaned up.
|
||||
*/
|
||||
public synchronized boolean push(String line, PythonScript script)
|
||||
throws PyException, IllegalStateException {
|
||||
|
||||
if (cleanedUp) {
|
||||
throw new IllegalStateException(
|
||||
"Ghidra python interpreter has already been cleaned up.");
|
||||
}
|
||||
|
||||
injectScriptHierarchy(script);
|
||||
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append("\n");
|
||||
}
|
||||
buffer.append(line);
|
||||
Py.getThreadState().tracefunc = interruptTraceFunction;
|
||||
Py.getSystemState().stderr = getSystemState().stderr; // needed to properly display SyntaxError
|
||||
boolean more;
|
||||
try {
|
||||
more = runsource(buffer.toString(), "python");
|
||||
getSystemState().stderr.invoke("flush");
|
||||
if (!more) {
|
||||
resetbuffer();
|
||||
}
|
||||
}
|
||||
catch (PyException pye) {
|
||||
resetbuffer();
|
||||
throw pye;
|
||||
}
|
||||
|
||||
return more;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a python file using this interpreter.
|
||||
*
|
||||
* @param file The python file to execute.
|
||||
* @param script A PythonScript from which we load state (or null).
|
||||
* @throws IllegalStateException if this interpreter has been cleaned up.
|
||||
*/
|
||||
public synchronized void execFile(ResourceFile file, PythonScript script)
|
||||
throws IllegalStateException {
|
||||
|
||||
if (cleanedUp) {
|
||||
throw new IllegalStateException(
|
||||
"Ghidra python interpreter has already been cleaned up.");
|
||||
}
|
||||
|
||||
injectScriptHierarchy(script);
|
||||
|
||||
Py.getThreadState().tracefunc = interruptTraceFunction;
|
||||
|
||||
// If the remote python debugger is alive, initialize it by calling settrace()
|
||||
if (!SystemUtilities.isInDevelopmentMode() && !SystemUtilities.isInHeadlessMode()) {
|
||||
if (PyDevUtils.getPyDevSrcDir() != null) {
|
||||
try {
|
||||
InetAddress localhost = InetAddress.getLocalHost();
|
||||
new Socket(localhost, PyDevUtils.PYDEV_REMOTE_DEBUGGER_PORT).close();
|
||||
Msg.info(this, "Python debugger found");
|
||||
exec("import pydevd; pydevd.settrace(host=\"" + localhost.getHostName() +
|
||||
"\", port=" + PyDevUtils.PYDEV_REMOTE_DEBUGGER_PORT + ", suspend=False);");
|
||||
Msg.info(this, "Connected to a python debugger.");
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.info(this, "Not connected to a python debugger.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run python file
|
||||
execfile(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cleanup() {
|
||||
super.cleanup();
|
||||
cleanedUp = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the given string to the interpreter's error stream with a newline
|
||||
* appended.
|
||||
*
|
||||
* @param str The string to print.
|
||||
*/
|
||||
void printErr(String str) {
|
||||
getSystemState().stderr.invoke("write", new PyString(str + "\n"));
|
||||
getSystemState().stderr.invoke("flush");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interpreter's primary prompt.
|
||||
*
|
||||
* @return The interpreter's primary prompt.
|
||||
*/
|
||||
synchronized String getPrimaryPrompt() {
|
||||
return getSystemState().ps1.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interprester's secondary prompt.
|
||||
*
|
||||
* @return The interpreter's secondary prompt.
|
||||
*/
|
||||
synchronized String getSecondaryPrompt() {
|
||||
return getSystemState().ps2.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a KeyboardInterrupt.
|
||||
* <p>
|
||||
* This will attempt to interrupt the interpreter if it is running. There are
|
||||
* two types of things this interrupt will work on:
|
||||
* <p>
|
||||
* 1: A batched series of python commands (such as a loop). This works by setting
|
||||
* our interrupt flag that is checked by our {@link InterruptTraceFunction} when
|
||||
* various trace events happen.
|
||||
* <p>
|
||||
* 2: A sleeping or otherwise interruptible python command. Since jython is all
|
||||
* java under the hood, a sleep is really just a {@link Thread#sleep}, which we can
|
||||
* kick with a {@link Thread#interrupt()}.
|
||||
* <p>
|
||||
* If another type of thing is taking a really long time, this interrupt will fail.
|
||||
*
|
||||
* @param pythonThread The Python Thread we need to interrupt.
|
||||
*/
|
||||
void interrupt(Thread pythonThread) {
|
||||
final long INTERRUPT_TIMEOUT = 5000;
|
||||
|
||||
if ((pythonThread != null) && pythonThread.isAlive()) {
|
||||
|
||||
// Set trace interrupt flag
|
||||
interrupt = Py.KeyboardInterrupt;
|
||||
|
||||
// Wake potentially sleeping python command
|
||||
pythonThread.interrupt();
|
||||
|
||||
try {
|
||||
pythonThread.join(INTERRUPT_TIMEOUT);
|
||||
if (pythonThread.isAlive()) {
|
||||
printErr("Cannot interrupt running command");
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Nothing to do
|
||||
}
|
||||
interrupt = null;
|
||||
}
|
||||
else {
|
||||
printErr("KeyboardInterrupt");
|
||||
}
|
||||
resetbuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects all of the accessible fields and methods found in the PythonScript class hierarchy into
|
||||
* the given interpreter's Python address space.
|
||||
*
|
||||
* @param script The script whose class hierarchy is to be used for injection.
|
||||
*/
|
||||
private void injectScriptHierarchy(PythonScript script) {
|
||||
|
||||
if (script == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop though the script class hierarchy
|
||||
for (Class<?> scriptClass = script.getClass(); scriptClass != Object.class; scriptClass =
|
||||
scriptClass.getSuperclass()) {
|
||||
|
||||
// Add public and protected fields
|
||||
for (Field field : scriptClass.getDeclaredFields()) {
|
||||
if (Modifier.isPublic(field.getModifiers()) ||
|
||||
Modifier.isProtected(field.getModifiers())) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
setVariable(field.getName(), field.get(script));
|
||||
}
|
||||
catch (IllegalAccessException iae) {
|
||||
throw new AssertException("Unexpected security manager being used!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add public methods only once. Ignore inner classes.
|
||||
if (!scriptMethodsInjected) {
|
||||
for (Method method : scriptClass.getDeclaredMethods()) {
|
||||
if (!method.getName().contains("$") &&
|
||||
Modifier.isPublic(method.getModifiers())) {
|
||||
method.setAccessible(true);
|
||||
setMethod(script, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scriptMethodsInjected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely sets a variable in the interpreter's namespace. This first checks to
|
||||
* make sure that we are not overriding a builtin Python symbol.
|
||||
*
|
||||
* @param varName The name of variable.
|
||||
* @param obj The value of the variable.
|
||||
* @return True if the variable was set; false if it already existed and wasn't set.
|
||||
*/
|
||||
private boolean setVariable(String varName, Object obj) {
|
||||
if (builtinModule.__findattr__(varName) == null) {
|
||||
set(varName, obj);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bound (callback/function pointer) method as a local variable in the interpreter.
|
||||
*
|
||||
* @param obj A Java object that contains the method to bind.
|
||||
* @param method The method from the object to bind.
|
||||
* @return True if the method was set; false if it already existed and wasn't set.
|
||||
*/
|
||||
private boolean setMethod(Object obj, Method method) {
|
||||
String methodName = method.getName();
|
||||
|
||||
// First, check to make sure we're not shadowing any internal Python keywords/functions/etc
|
||||
if (builtinModule.__findattr__(methodName) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, we're safe to set it
|
||||
PyObject pyObj = get(methodName);
|
||||
if ((null == pyObj) || (pyObj instanceof PyNone)) {
|
||||
// This is the first method of this name that we are adding. Create a new bound PyMethod
|
||||
// to bind the Java method to the Java object in the Python world.
|
||||
set(methodName, new PyMethod(new PyReflectedFunction(method), Py.java2py(obj),
|
||||
Py.java2py(obj.getClass())));
|
||||
}
|
||||
else if (pyObj instanceof PyMethod) {
|
||||
// Another method of this name has already been added. Add it to the list of possibilities
|
||||
// (different arguments to methods on the same Object). But first, we must do some sanity
|
||||
// checks.
|
||||
PyMethod pyMethod = (PyMethod) pyObj;
|
||||
if ((pyMethod.__self__._is(Py.java2py(obj))) != Py.True) {
|
||||
Msg.error(this,
|
||||
"Method " + methodName + " of " + obj + " attempting to shadow method " +
|
||||
pyMethod.__func__ + " of " + pyMethod.__self__);
|
||||
return false;
|
||||
}
|
||||
if (!(pyMethod.__func__ instanceof PyReflectedFunction)) {
|
||||
Msg.error(this, "For addition of method " + methodName + " of " + obj +
|
||||
", cannot mix with non Java function " + pyMethod.__func__);
|
||||
return false;
|
||||
}
|
||||
((PyReflectedFunction) pyMethod.__func__).addMethod(method);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the possible command completions for a command.
|
||||
*
|
||||
* @param cmd The command line.
|
||||
* @param includeBuiltins True if we should include python built-ins; otherwise, false.
|
||||
* @return A list of possible command completions. Could be empty if there aren't any.
|
||||
* @see PythonPlugin#getCompletions
|
||||
*/
|
||||
List<CodeCompletion> getCommandCompletions(String cmd, boolean includeBuiltins) {
|
||||
if ((cmd.length() > 0) && (cmd.charAt(cmd.length() - 1) == '(')) {
|
||||
return getMethodCommandCompletions(cmd);
|
||||
}
|
||||
return getPropertyCommandCompletions(cmd, includeBuiltins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns method documentation for the current command.
|
||||
*
|
||||
* @param cmd the current command
|
||||
* @return method documentation for the current command
|
||||
*/
|
||||
private List<CodeCompletion> getMethodCommandCompletions(String cmd) {
|
||||
List<CodeCompletion> completion_list = new ArrayList<>();
|
||||
try {
|
||||
PyObject getCallTipJava = introspectModule.__findattr__("getCallTipJava");
|
||||
PyString command = new PyString(cmd);
|
||||
PyObject locals = getLocals();
|
||||
|
||||
// Return value is (name, argspec, tip_text)
|
||||
ListIterator<?> iter =
|
||||
((List<?>) getCallTipJava.__call__(command, locals)).listIterator();
|
||||
while (iter.hasNext()) {
|
||||
String completion_portion = iter.next().toString();
|
||||
if (!completion_portion.equals("")) {
|
||||
String[] substrings = completion_portion.split("\n");
|
||||
for (String substring : substrings) {
|
||||
completion_list.add(new CodeCompletion(substring, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
}
|
||||
return completion_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of property->string_substitution pairs.
|
||||
*
|
||||
* @param cmd current command
|
||||
* @param includeBuiltins True if we should include python built-ins; otherwise, false.
|
||||
* @return A list of possible command completions. Could be empty if there aren't any.
|
||||
*/
|
||||
private List<CodeCompletion> getPropertyCommandCompletions(String cmd,
|
||||
boolean includeBuiltins) {
|
||||
try {
|
||||
PyObject getAutoCompleteList = introspectModule.__findattr__("getAutoCompleteList");
|
||||
PyString command = new PyString(cmd);
|
||||
PyStringMap locals = ((PyStringMap) getLocals()).copy();
|
||||
if (includeBuiltins) {
|
||||
// Add in the __builtin__ module's contents for the search
|
||||
locals.update(builtinModule.__dict__);
|
||||
}
|
||||
List<?> list = (List<?>) getAutoCompleteList.__call__(command, locals);
|
||||
return CollectionUtils.asList(list, CodeCompletion.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom trace function that allows interruption of python code to occur when various code
|
||||
* paths are encountered.
|
||||
*/
|
||||
class InterruptTraceFunction extends TraceFunction {
|
||||
private void checkInterrupt() {
|
||||
if (interrupt != null) {
|
||||
throw Py.makeException(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceCall(PyFrame frame) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceReturn(PyFrame frame, PyObject ret) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceLine(PyFrame frame, int line) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceException(PyFrame frame, PyException exc) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PyDevUtils {
|
||||
|
||||
public static final int PYDEV_REMOTE_DEBUGGER_PORT = 5678;
|
||||
|
||||
/**
|
||||
* Gets The PyDev source directory.
|
||||
*
|
||||
* @return The PyDev source directory, or null if it not known.
|
||||
*/
|
||||
public static File getPyDevSrcDir() {
|
||||
String property = System.getProperty("eclipse.pysrc.dir");
|
||||
return StringUtils.isNotBlank(property) ? new File(property) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent instantiation of utility class.
|
||||
*/
|
||||
private PyDevUtils() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import org.python.core.PyInstance;
|
||||
import org.python.core.PyObject;
|
||||
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Generates CodeCompletions from Python objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class PythonCodeCompletionFactory {
|
||||
private static List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||
private static Map<Class<?>, Color> classToColorMap = new HashMap<Class<?>, Color>();
|
||||
/* necessary because we only want to show the user the simple class name
|
||||
* Well, that, and the Options.DELIMITER is a '.' which totally messes
|
||||
* things up.
|
||||
*/
|
||||
private static Map<String, Class<?>> simpleNameToClass = new HashMap<String, Class<?>>();
|
||||
private static Map<Class<?>, String> classDescription = new HashMap<Class<?>, String>();
|
||||
public static final String COMPLETION_LABEL = "Code Completion Colors";
|
||||
|
||||
/* package-level accessibility so that PythonPlugin can tell this is
|
||||
* our option
|
||||
*/
|
||||
final static String INCLUDE_TYPES_LABEL = "Include type names in code completion popup?";
|
||||
private final static String INCLUDE_TYPES_DESCRIPTION =
|
||||
"Whether or not to include the type names (classes) of the possible "
|
||||
+ "completions in the code completion window. The class name will be "
|
||||
+ "parenthesized after the completion.";
|
||||
private final static boolean INCLUDE_TYPES_DEFAULT = true;
|
||||
private static boolean includeTypes = INCLUDE_TYPES_DEFAULT;
|
||||
|
||||
public static final Color NULL_COLOR = new Color(255, 0, 0);
|
||||
public static final Color FUNCTION_COLOR = new Color(0, 128, 0);
|
||||
public static final Color PACKAGE_COLOR = new Color(128, 0, 0);
|
||||
public static final Color CLASS_COLOR = new Color(0, 0, 255);
|
||||
public static final Color METHOD_COLOR = new Color(0, 128, 128);
|
||||
/* anonymous code chunks */
|
||||
public static final Color CODE_COLOR = new Color(0, 64, 0);
|
||||
public static final Color INSTANCE_COLOR = new Color(128, 0, 128);
|
||||
public static final Color SEQUENCE_COLOR = new Color(128, 96, 64);
|
||||
public static final Color MAP_COLOR = new Color(64, 96, 128);
|
||||
public static final Color NUMBER_COLOR = new Color(64, 64, 64);
|
||||
/* for weird Jython-specific stuff */
|
||||
public static final Color SPECIAL_COLOR = new Color(64, 96, 64);
|
||||
|
||||
static {
|
||||
/* Order matters! This is the order in which classes are checked for
|
||||
* coloring.
|
||||
*/
|
||||
setupClass("org.python.core.PyNone", NULL_COLOR, "'None' (null) Objects");
|
||||
|
||||
setupClass("org.python.core.PyReflectedFunction", FUNCTION_COLOR,
|
||||
"Python functions written in Java");
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.BuiltinFunctions", FUNCTION_COLOR,
|
||||
// "Python's built-in functions collection (note that many are " +
|
||||
// "re-implemented in Java)");
|
||||
setupClass("org.python.core.__builtin__", FUNCTION_COLOR,
|
||||
"Python's built-in functions collection (note that many are "
|
||||
+ "re-implemented in Java)");
|
||||
setupClass("org.python.core.PyFunction", FUNCTION_COLOR, "functions written in Python");
|
||||
setupClass("org.python.core.PyMethodDescr", FUNCTION_COLOR,
|
||||
"unbound Python builtin instance methods (they take an "
|
||||
+ "Object as the first argument)");
|
||||
|
||||
setupClass("org.python.core.PyJavaPackage", PACKAGE_COLOR, "Java packages");
|
||||
setupClass("org.python.core.PyModule", PACKAGE_COLOR, "Python modules");
|
||||
|
||||
/* Even though the latter is a subclass of the former, this allows
|
||||
* the user to differentiate visually Java classes from Python classes
|
||||
* if they so wish. But we don't do this by default.
|
||||
*/
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.PyJavaClass", CLASS_COLOR,
|
||||
// "Java classes");
|
||||
setupClass("org.python.core.PyJavaType", CLASS_COLOR, "Java classes");
|
||||
setupClass("org.python.core.PyClass", CLASS_COLOR, "Python classes");
|
||||
setupClass("org.python.core.PyType", CLASS_COLOR, "core Python types");
|
||||
|
||||
setupClass("org.python.core.PyMethod", METHOD_COLOR, "methods");
|
||||
setupClass("org.python.core.PyBuiltinFunction", METHOD_COLOR,
|
||||
"core Python methods, often inherited from Python's Object "
|
||||
+ "(overriding these methods is very powerful)");
|
||||
|
||||
setupClass("org.python.core.PySequence", SEQUENCE_COLOR,
|
||||
"iterable sequences, including arrays, list, and strings");
|
||||
|
||||
setupClass("org.python.core.PyDictionary", MAP_COLOR, "arbitrary Python mapping type");
|
||||
setupClass("org.python.core.PyStringMap", MAP_COLOR, "Python String->Object mapping type");
|
||||
|
||||
setupClass("org.python.core.PyInteger", NUMBER_COLOR, "integers");
|
||||
setupClass("org.python.core.PyLong", NUMBER_COLOR, "long integers");
|
||||
setupClass("org.python.core.PyFloat", NUMBER_COLOR, "floating-point (decimal) numbers");
|
||||
setupClass("org.python.core.PyComplex", NUMBER_COLOR, "complex numbers");
|
||||
|
||||
setupClass("org.python.core.PyCompoundCallable", SPECIAL_COLOR,
|
||||
"special Python properties for "
|
||||
+ "assigning Python functions as EventListeners on Java objects");
|
||||
|
||||
/* changed for Jython 2.5 */
|
||||
setupClass("org.python.core.PyObjectDerived", INSTANCE_COLOR, "Java Objects");
|
||||
setupClass("org.python.core.PyInstance", INSTANCE_COLOR, "Python Objects");
|
||||
|
||||
setupClass("org.python.core.PyCode", CODE_COLOR, "chunks of Python code");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param klass a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(Class<?> klass) {
|
||||
return getSimpleName(klass.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param className name of a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(String className) {
|
||||
/* lastIndexOf returns -1 on not found, so this works whether or not
|
||||
* a period is actually in className
|
||||
*/
|
||||
return className.substring(className.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a Class mapping.
|
||||
*
|
||||
* @param className Class name
|
||||
* @param defaultColor default Color for this Class
|
||||
* @param description description of the Class
|
||||
*/
|
||||
private static void setupClass(String className, Color defaultColor, String description) {
|
||||
try {
|
||||
Class<?> klass = Class.forName(className);
|
||||
classes.add(klass);
|
||||
classToColorMap.put(klass, defaultColor);
|
||||
simpleNameToClass.put(getSimpleName(klass), klass);
|
||||
classDescription.put(klass, description);
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
Msg.debug(PythonCodeCompletionFactory.class, "Unable to find class: " + className, cnfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CodeCompletion from the given Python objects.
|
||||
*
|
||||
* @param description description of the new CodeCompletion
|
||||
* @param insertion what will be inserted to make the code complete
|
||||
* @param pyObj a Python Object
|
||||
* @return A new CodeCompletion from the given Python objects.
|
||||
*/
|
||||
public static CodeCompletion newCodeCompletion(String description, String insertion,
|
||||
PyObject pyObj) {
|
||||
JComponent comp = null;
|
||||
|
||||
if (pyObj != null) {
|
||||
if (includeTypes) {
|
||||
/* append the class name to the end of the description */
|
||||
String className = getSimpleName(pyObj.getClass());
|
||||
if (pyObj instanceof PyInstance) {
|
||||
/* get the real class */
|
||||
className = getSimpleName(((PyInstance) pyObj).instclass.__name__);
|
||||
}
|
||||
else if (className.startsWith("Py")) {
|
||||
/* strip off the "Py" */
|
||||
className = className.substring("Py".length());
|
||||
}
|
||||
description = description + " (" + className + ")";
|
||||
}
|
||||
|
||||
comp = new JLabel(description);
|
||||
Iterator<Class<?>> iter = classes.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Class<?> testClass = iter.next();
|
||||
if (testClass.isInstance(pyObj)) {
|
||||
comp.setForeground(classToColorMap.get(testClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new CodeCompletion(description, insertion, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Python code completion Options.
|
||||
* @param plugin python plugin as options owner
|
||||
* @param options an Options handle
|
||||
*/
|
||||
public static void setupOptions(PythonPlugin plugin, Options options) {
|
||||
includeTypes = options.getBoolean(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT);
|
||||
options.registerOption(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT, null,
|
||||
INCLUDE_TYPES_DESCRIPTION);
|
||||
|
||||
Iterator<?> iter = classes.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Class<?> currentClass = (Class<?>) iter.next();
|
||||
options.registerOption(COMPLETION_LABEL + Options.DELIMITER +
|
||||
getSimpleName(currentClass), classToColorMap.get(currentClass), null,
|
||||
"Color to use for " + classDescription.get(currentClass) + ".");
|
||||
classToColorMap.put(currentClass, options.getColor(COMPLETION_LABEL +
|
||||
Options.DELIMITER + getSimpleName(currentClass), classToColorMap.get(currentClass)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an Option change.
|
||||
*
|
||||
* This is named slightly differently because it is a static method, not
|
||||
* an instance method.
|
||||
*
|
||||
* By the time we get here, we assume that the Option changed is indeed
|
||||
* ours.
|
||||
*
|
||||
* @param options the Options handle
|
||||
* @param name name of the Option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
public static void changeOptions(Options options, String name, Object oldValue, Object newValue) {
|
||||
String classSimpleName = name.substring((COMPLETION_LABEL + Options.DELIMITER).length());
|
||||
Class<?> klass = simpleNameToClass.get(classSimpleName);
|
||||
|
||||
if (classToColorMap.containsKey(klass)) {
|
||||
classToColorMap.put(klass, (Color) newValue);
|
||||
}
|
||||
else if (name.equals(INCLUDE_TYPES_LABEL)) {
|
||||
includeTypes = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
else {
|
||||
Msg.error(PythonCodeCompletionFactory.class, "unknown option '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Java __call__ methods declared for a Python object.
|
||||
*
|
||||
* Some Python "methods" in the new-style Python objects are actually
|
||||
* classes in and of themselves, re-implementing __call__ methods to
|
||||
* tell us how to call them. This returns an array of those Methods
|
||||
* (for code completion help).
|
||||
*
|
||||
* @param obj a PyObject
|
||||
* @return the Java __call__ methods declared for the Python object
|
||||
*/
|
||||
public static Object[] getCallMethods(PyObject obj) {
|
||||
List<Method> callMethodList = new ArrayList<Method>();
|
||||
Method[] declaredMethods = obj.getClass().getDeclaredMethods();
|
||||
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
if (declaredMethod.getName().equals("__call__")) {
|
||||
callMethodList.add(declaredMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return callMethodList.toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.python.core.PySystemState;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
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.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.task.*;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* This plugin provides the interactive Python interpreter.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.INTERPRETERS,
|
||||
shortDescription = "Python Interpreter",
|
||||
description = "Provides an interactive Python Interpreter that is tightly integrated with a loaded Ghidra program.",
|
||||
servicesRequired = { InterpreterPanelService.class },
|
||||
isSlowInstallation = true
|
||||
)
|
||||
//@formatter:on
|
||||
public class PythonPlugin extends ProgramPlugin
|
||||
implements InterpreterConnection, OptionsChangeListener {
|
||||
|
||||
private InterpreterConsole console;
|
||||
private GhidraPythonInterpreter interpreter;
|
||||
private PythonScript interactiveScript;
|
||||
private TaskMonitor interactiveTaskMonitor;
|
||||
private PythonPluginInputThread inputThread;
|
||||
|
||||
// Plugin options
|
||||
private final static String INCLUDE_BUILTINS_LABEL = "Include \"builtins\" in code completion?";
|
||||
private final static String INCLUDE_BUILTINS_DESCRIPTION =
|
||||
"Whether or not to include Python's built-in functions and properties in the pop-up code completion window.";
|
||||
private final static boolean INCLUDE_BUILTINS_DEFAULT = true;
|
||||
private boolean includeBuiltins = INCLUDE_BUILTINS_DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates a new PythonPlugin object.
|
||||
*
|
||||
* @param tool The tool associated with this plugin.
|
||||
*/
|
||||
public PythonPlugin(PluginTool tool) {
|
||||
super(tool, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interpreter console.
|
||||
*
|
||||
* @return The plugin's interpreter console.
|
||||
*/
|
||||
InterpreterConsole getConsole() {
|
||||
return console;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's Python interpreter.
|
||||
*
|
||||
* @return The plugin's Python interpreter. May be null.
|
||||
*/
|
||||
GhidraPythonInterpreter getInterpreter() {
|
||||
return interpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive script
|
||||
*
|
||||
* @return The plugin's interactive script.
|
||||
*/
|
||||
PythonScript getInteractiveScript() {
|
||||
return interactiveScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive task monitor.
|
||||
*
|
||||
* @return The plugin's interactive task monitor.
|
||||
*/
|
||||
TaskMonitor getInteractiveTaskMonitor() {
|
||||
return interactiveTaskMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
console =
|
||||
getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false);
|
||||
welcome();
|
||||
console.addFirstActivationCallback(() -> resetInterpreter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the interpreter to a new starting state. This is used when the plugin is first
|
||||
* initialized, as well as when an existing interpreter receives a Python exit command.
|
||||
* We used to try to reset the same interpreter, but it was really hard to do that correctly
|
||||
* so we now just create a brand new one.
|
||||
* <p>
|
||||
* NOTE: Loading Jython for the first time can be quite slow the first time, so we do this
|
||||
* when the user wants to first interact with the interpreter (rather than when the plugin loads).
|
||||
*/
|
||||
private void resetInterpreter() {
|
||||
|
||||
TaskLauncher.launchModal("Resetting Python...", () -> {
|
||||
resetInterpreterInBackground();
|
||||
});
|
||||
}
|
||||
|
||||
// we expect this to be called from off the Swing thread
|
||||
private void resetInterpreterInBackground() {
|
||||
|
||||
// Reset the interpreter by creating a new one. Clean up the old one if present.
|
||||
if (interpreter == null) {
|
||||
|
||||
// Setup options
|
||||
ToolOptions options = tool.getOptions("Python");
|
||||
includeBuiltins = options.getBoolean(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT);
|
||||
options.registerOption(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT, null,
|
||||
INCLUDE_BUILTINS_DESCRIPTION);
|
||||
options.addOptionsChangeListener(this);
|
||||
|
||||
interpreter = GhidraPythonInterpreter.get();
|
||||
|
||||
// Setup code completion. This currently has to be done after the interpreter
|
||||
// is created. Otherwise an exception will occur.
|
||||
PythonCodeCompletionFactory.setupOptions(this, options);
|
||||
}
|
||||
else {
|
||||
inputThread.dispose();
|
||||
interpreter.cleanup();
|
||||
interpreter = GhidraPythonInterpreter.get();
|
||||
}
|
||||
|
||||
// Reset the console.
|
||||
console.clear();
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
|
||||
// Tie the interpreter's input/output to the plugin's console.
|
||||
interpreter.setIn(console.getStdin());
|
||||
interpreter.setOut(console.getStdOut());
|
||||
interpreter.setErr(console.getStdErr());
|
||||
|
||||
// Print a welcome message.
|
||||
welcome();
|
||||
|
||||
// Setup the PythonScript describing the state of the interactive prompt.
|
||||
// This allows things like currentProgram and currentAddress to dynamically reflect
|
||||
// what's happening in the listing.
|
||||
interactiveScript = new PythonScript();
|
||||
interactiveTaskMonitor = new PythonInteractiveTaskMonitor(console.getStdOut());
|
||||
|
||||
// Start the input thread that receives python commands to execute.
|
||||
inputThread = new PythonPluginInputThread(this);
|
||||
inputThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change in one of our options.
|
||||
*
|
||||
* @param options the options handle
|
||||
* @param optionName name of the option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
if (optionName.startsWith(PythonCodeCompletionFactory.COMPLETION_LABEL)) {
|
||||
PythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(PythonCodeCompletionFactory.INCLUDE_TYPES_LABEL)) {
|
||||
PythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(INCLUDE_BUILTINS_LABEL)) {
|
||||
includeBuiltins = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible command completion values.
|
||||
*
|
||||
* @param cmd current command line (without prompt)
|
||||
* @return A list of possible command completion values. Could be empty if there aren't any.
|
||||
*/
|
||||
@Override
|
||||
public List<CodeCompletion> getCompletions(String cmd) {
|
||||
// Refresh the environment
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("python")));
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), currentProgram, currentLocation,
|
||||
currentSelection, currentHighlight),
|
||||
interactiveTaskMonitor, console.getOutWriter());
|
||||
|
||||
return interpreter.getCommandCompletions(cmd, includeBuiltins);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
// Terminate the input thread
|
||||
if (inputThread != null) {
|
||||
inputThread.dispose();
|
||||
inputThread = null;
|
||||
}
|
||||
|
||||
// Dispose of the console
|
||||
if (console != null) {
|
||||
console.dispose();
|
||||
console = null;
|
||||
}
|
||||
|
||||
// Cleanup the interpreter
|
||||
if (interpreter != null) {
|
||||
interpreter.cleanup();
|
||||
interpreter = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
if (interpreter == null) {
|
||||
return;
|
||||
}
|
||||
interpreter.interrupt(inputThread.getPythonPluginExecutionThread());
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
resetInterpreter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Python";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPluginDescription().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageIcon getIcon() {
|
||||
return ResourceManager.loadImage("images/python.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a welcome message to the console.
|
||||
*/
|
||||
private void welcome() {
|
||||
console.getOutWriter().println("Python Interpreter for Ghidra");
|
||||
console.getOutWriter().println("Based on Jython version " + PySystemState.version);
|
||||
console.getOutWriter().println("Press 'F1' for usage instructions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for cancelling execution using a TaskMonitor.
|
||||
*/
|
||||
class PythonInteractiveTaskMonitor extends TaskMonitorAdapter {
|
||||
private PrintWriter output = null;
|
||||
|
||||
public PythonInteractiveTaskMonitor(PrintWriter stdOut) {
|
||||
output = stdOut;
|
||||
}
|
||||
|
||||
public PythonInteractiveTaskMonitor(OutputStream stdout) {
|
||||
this(new PrintWriter(stdout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
output.println("<python-interactive>: " + message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a python command for the plugin.
|
||||
*/
|
||||
class PythonPluginExecutionThread extends Thread {
|
||||
|
||||
private PythonPlugin plugin;
|
||||
private String cmd;
|
||||
private AtomicBoolean moreInputWanted;
|
||||
|
||||
/**
|
||||
* Creates a new python plugin execution thread that executes the given command for the given plugin.
|
||||
*
|
||||
* @param plugin The python plugin to execute the command for.
|
||||
* @param cmd The python command to execute.
|
||||
* @param moreInputWanted Gets set to indicate that the executed command expects more input.
|
||||
*/
|
||||
PythonPluginExecutionThread(PythonPlugin plugin, String cmd, AtomicBoolean moreInputWanted) {
|
||||
super("Python plugin execution thread");
|
||||
|
||||
this.plugin = plugin;
|
||||
this.cmd = cmd;
|
||||
this.moreInputWanted = moreInputWanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
TaskMonitor interactiveTaskMonitor = plugin.getInteractiveTaskMonitor();
|
||||
PythonScript interactiveScript = plugin.getInteractiveScript();
|
||||
Program program = plugin.getCurrentProgram();
|
||||
|
||||
// Setup transaction for the execution.
|
||||
int transaction_number = -1;
|
||||
if (program != null) {
|
||||
transaction_number = program.startTransaction("Python command");
|
||||
}
|
||||
|
||||
// Setup Ghidra state to be passed into interpreter
|
||||
interactiveTaskMonitor.clearCanceled();
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("python")));
|
||||
PluginTool tool = plugin.getTool();
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), program, plugin.getProgramLocation(),
|
||||
plugin.getProgramSelection(), plugin.getProgramHighlight()),
|
||||
interactiveTaskMonitor, new PrintWriter(plugin.getConsole().getStdOut()));
|
||||
|
||||
// Execute the command
|
||||
boolean commit = false;
|
||||
moreInputWanted.set(false);
|
||||
try {
|
||||
moreInputWanted.set(plugin.getInterpreter().push(cmd, plugin.getInteractiveScript()));
|
||||
commit = true;
|
||||
}
|
||||
catch (PyException pye) {
|
||||
String exceptionName = PyException.exceptionClassName(pye.type);
|
||||
if (exceptionName.equalsIgnoreCase("exceptions.SystemExit")) {
|
||||
plugin.reset();
|
||||
}
|
||||
else {
|
||||
plugin.getConsole().getErrWriter().println("Suppressing exception: " +
|
||||
PyException.exceptionClassName(pye.type));
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
plugin.getConsole().getErrWriter().println("Stack overflow!");
|
||||
}
|
||||
finally {
|
||||
// Re-get the current program in case the user closed the program while a long running
|
||||
// command was executing.
|
||||
program = plugin.getCurrentProgram();
|
||||
if (program != null) {
|
||||
program.endTransaction(transaction_number, commit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Thread responsible for getting interactive lines of python from the plugin.
|
||||
* This class also kicks off the execution of that line in a new {@link PythonPluginExecutionThread}.
|
||||
*/
|
||||
class PythonPluginInputThread extends Thread {
|
||||
|
||||
private static int generationCount = 0;
|
||||
|
||||
private PythonPlugin plugin;
|
||||
private PythonPluginExecutionThread pythonExecutionThread;
|
||||
private AtomicBoolean moreInputWanted;
|
||||
private AtomicBoolean shouldContinue;
|
||||
|
||||
/**
|
||||
* Creates a new python input thread that gets a line of python input from the given plugin.
|
||||
*
|
||||
* @param plugin The python plugin to get input from.
|
||||
*/
|
||||
PythonPluginInputThread(PythonPlugin plugin) {
|
||||
super("Python plugin input thread (generation " + ++generationCount + ")");
|
||||
this.plugin = plugin;
|
||||
this.moreInputWanted = new AtomicBoolean(false);
|
||||
this.shouldContinue = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last python plugin execution thread that ran.
|
||||
*
|
||||
* @return The last python plugin execution thread that ran. Could be null if one never ran.
|
||||
*/
|
||||
PythonPluginExecutionThread getPythonPluginExecutionThread() {
|
||||
return pythonExecutionThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(plugin.getConsole().getStdin()))) {
|
||||
while (shouldContinue.get()) {
|
||||
|
||||
// Read a line from the console. Do it non-blocking so we can exit the thread
|
||||
// if we were instructed to not continue.
|
||||
String line;
|
||||
if (plugin.getConsole().getStdin().available() > 0) {
|
||||
line = reader.readLine();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Nothing to do...just continue.
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Execute the line in a new thread
|
||||
pythonExecutionThread = new PythonPluginExecutionThread(plugin, line, moreInputWanted);
|
||||
pythonExecutionThread.start();
|
||||
|
||||
try {
|
||||
// Wait for the execution to finish
|
||||
pythonExecutionThread.join();
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// Hey we're back... a little earlier than expected, but there must be a reason.
|
||||
// So we'll go quietly.
|
||||
}
|
||||
|
||||
// Set the prompt appropriately
|
||||
plugin.getConsole().setPrompt(
|
||||
moreInputWanted.get() ? plugin.getInterpreter().getSecondaryPrompt()
|
||||
: plugin.getInterpreter().getPrimaryPrompt());
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(PythonPluginInputThread.class,
|
||||
"Internal error reading commands from interpreter console. Please reset the interpreter.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the thread by telling it to not continue asking for input.
|
||||
*/
|
||||
void dispose() {
|
||||
shouldContinue.set(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.python.util.jython;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Launcher entry point for running Ghidra from within Jython.
|
||||
*/
|
||||
public class PythonRun implements GhidraLaunchable {
|
||||
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) {
|
||||
|
||||
// Initialize the application
|
||||
ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration();
|
||||
Application.initializeApplication(layout, configuration);
|
||||
|
||||
// Setup python home directory
|
||||
try {
|
||||
PythonUtils.setupPythonHomeDir();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(PythonRun.class, null, "Python home directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Setup python cache directory
|
||||
try {
|
||||
PythonUtils.setupPythonCacheDir(configuration.getTaskMonitor());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(PythonRun.class, null, "Python cache directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.showError(PythonRun.class, null, "Operation cancelled", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Pass control to Jython
|
||||
jython.main(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* A Python version of a {@link GhidraScript}.
|
||||
*/
|
||||
public class PythonScript extends GhidraScript {
|
||||
|
||||
static final String PYTHON_INTERPRETER = "ghidra.python.interpreter";
|
||||
|
||||
private AtomicBoolean interpreterRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// Try to get the interpreter from an existing script state.
|
||||
GhidraPythonInterpreter interpreter =
|
||||
(GhidraPythonInterpreter) state.getEnvironmentVar(PYTHON_INTERPRETER);
|
||||
|
||||
// Are we being called from an already running PythonScript with existing state?
|
||||
if (interpreter != null) {
|
||||
runInExistingEnvironment(interpreter);
|
||||
}
|
||||
else {
|
||||
runInNewEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runScript(String scriptName, GhidraState scriptState) throws Exception {
|
||||
GhidraPythonInterpreter interpreter =
|
||||
(GhidraPythonInterpreter) state.getEnvironmentVar(PYTHON_INTERPRETER);
|
||||
if (interpreter == null) {
|
||||
interpreter = GhidraPythonInterpreter.get();
|
||||
if (interpreter == null) {
|
||||
throw new AssertException("Could not get Ghidra Python interpreter!");
|
||||
}
|
||||
}
|
||||
|
||||
for (ResourceFile dir : GhidraScriptUtil.getScriptSourceDirectories()) {
|
||||
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
|
||||
if (scriptSource.exists()) {
|
||||
GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource);
|
||||
GhidraScript ghidraScript = provider.getScriptInstance(scriptSource, writer);
|
||||
if (ghidraScript == null) {
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
updateStateFromVariables();
|
||||
}
|
||||
|
||||
if (ghidraScript instanceof PythonScript) {
|
||||
ghidraScript.set(scriptState, monitor, writer);
|
||||
PythonScript pythonScript = (PythonScript) ghidraScript;
|
||||
interpreter.execFile(pythonScript.getSourceFile(), pythonScript);
|
||||
}
|
||||
else {
|
||||
ghidraScript.execute(scriptState, monitor, writer);
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
loadVariablesFromState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in an existing interpreter environment.
|
||||
*
|
||||
* @param interpreter The existing interpreter to execute from.
|
||||
*/
|
||||
private void runInExistingEnvironment(GhidraPythonInterpreter interpreter) {
|
||||
interpreter.execFile(sourceFile, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in a new interpreter environment and sticks the new interpreter
|
||||
* in the script state so it can be retrieved by scripts called from this script.
|
||||
*/
|
||||
private void runInNewEnvironment() {
|
||||
|
||||
// Create new interpreter and stick it in the script's state.
|
||||
final GhidraPythonInterpreter interpreter = GhidraPythonInterpreter.get();
|
||||
final PrintWriter stdout = getStdOut();
|
||||
final PrintWriter stderr = getStdErr();
|
||||
interpreter.setOut(stdout);
|
||||
interpreter.setErr(stderr);
|
||||
|
||||
// We stick the interpreter in the state so that if the script calls runScript, that
|
||||
// script will use the same interpreter. It is questionable whether or not we should do
|
||||
// this (the new script will get all of the old script's variables), but changing it now
|
||||
// could break people's scripts if they expect this behavior.
|
||||
state.addEnvironmentVar(PYTHON_INTERPRETER, interpreter);
|
||||
|
||||
// Execute the script in a new thread.
|
||||
PythonScriptExecutionThread executionThread =
|
||||
new PythonScriptExecutionThread(this, interpreter, interpreterRunning);
|
||||
interpreterRunning.set(true);
|
||||
executionThread.start();
|
||||
|
||||
// Wait for the script be finish running
|
||||
while (interpreterRunning.get() && !monitor.isCancelled()) {
|
||||
Thread.yield();
|
||||
sleep100millis();
|
||||
}
|
||||
if (interpreterRunning.get()) {
|
||||
// We've been canceled. Interrupt the interpreter.
|
||||
interpreter.interrupt(executionThread);
|
||||
}
|
||||
|
||||
// Script is done. Make sure the output displays.
|
||||
stderr.flush();
|
||||
stdout.flush();
|
||||
|
||||
// Cleanup the interpreter, and remove it from the state (once it's cleaned it cannot be
|
||||
// reused)
|
||||
interpreter.cleanup();
|
||||
state.removeEnvironmentVar(PYTHON_INTERPRETER);
|
||||
}
|
||||
|
||||
private PrintWriter getStdOut() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdOut();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.out, true);
|
||||
}
|
||||
|
||||
private PrintWriter getStdErr() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdErr();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.err, true);
|
||||
}
|
||||
|
||||
private void sleep100millis() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Don't care; will probably be called again
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Python";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a python script from a file.
|
||||
*/
|
||||
class PythonScriptExecutionThread extends Thread {
|
||||
|
||||
private PythonScript script;
|
||||
private GhidraPythonInterpreter interpreter;
|
||||
private AtomicBoolean interpreterRunning;
|
||||
|
||||
/**
|
||||
* Creates a new python script execution thread that executes the given python script.
|
||||
*
|
||||
* @param script The python script to execute.
|
||||
* @param interpreter The python interpreter to use for execution.
|
||||
* @param interpreterRunning Gets set to indicate whether or not the interpreter is still running the script.
|
||||
*/
|
||||
PythonScriptExecutionThread(PythonScript script, GhidraPythonInterpreter interpreter,
|
||||
AtomicBoolean interpreterRunning) {
|
||||
super("Python script execution thread");
|
||||
|
||||
this.script = script;
|
||||
this.interpreter = interpreter;
|
||||
this.interpreterRunning = interpreterRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
interpreter.execFile(script.getSourceFile(), script);
|
||||
}
|
||||
catch (PyException pye) {
|
||||
if (PyException.exceptionClassName(pye.type).equalsIgnoreCase(
|
||||
"exceptions.SystemExit")) {
|
||||
interpreter.printErr("SystemExit");
|
||||
}
|
||||
else {
|
||||
pye.printStackTrace(); // this prints to the interpreter error stream.
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
interpreter.printErr("Stack overflow!");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
interpreter.printErr(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
interpreterRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* 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.python;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.script.GhidraScriptProvider;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class PythonScriptProvider extends GhidraScriptProvider {
|
||||
|
||||
@Override
|
||||
public void createNewScript(ResourceFile newScript, String category) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)));
|
||||
writeHeader(writer, category);
|
||||
writer.println("");
|
||||
writeBody(writer);
|
||||
writer.println("");
|
||||
writer.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentCharacter() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Python";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return ".py";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
||||
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||
|
||||
Class<?> clazz = Class.forName(PythonScript.class.getName());
|
||||
GhidraScript script = (GhidraScript) clazz.newInstance();
|
||||
script.setSourceFile(sourceFile);
|
||||
return script;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Python utility method class.
|
||||
*/
|
||||
public class PythonUtils {
|
||||
|
||||
public static final String PYTHON_NAME = "jython-2.7.1";
|
||||
public static final String PYTHON_CACHEDIR = "jython_cachedir";
|
||||
public static final String PYTHON_SRC = "python-src";
|
||||
|
||||
/**
|
||||
* Sets up the python home directory. This is the directory that has the "Lib" directory in it.
|
||||
*
|
||||
* @return The python home directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the home directory.
|
||||
*/
|
||||
public static File setupPythonHomeDir() throws IOException {
|
||||
|
||||
File pythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File pythonHomeDir =
|
||||
Application.getModuleDataSubDirectory(pythonModuleDir.getName(), PYTHON_NAME).getFile(
|
||||
false);
|
||||
|
||||
if (!pythonHomeDir.exists()) {
|
||||
throw new IOException("Failed to find the python home directory at: " + pythonHomeDir);
|
||||
}
|
||||
|
||||
System.setProperty("python.home", pythonHomeDir.getAbsolutePath());
|
||||
|
||||
return pythonHomeDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the python cache directory. This is a temporary space that python source files
|
||||
* get compiled to and cached. It should NOT be in the Ghidra installation directory, because
|
||||
* some installations will not have the appropriate directory permissions to create new files in.
|
||||
*
|
||||
* @param monitor A monitor to use during the cache directory setup.
|
||||
* @return The python cache directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the cache directory.
|
||||
* @throws CancelledException If the user cancelled the setup.
|
||||
*/
|
||||
public static File setupPythonCacheDir(TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
|
||||
File cacheDir = new File(Application.getUserTempDirectory(), PYTHON_CACHEDIR);
|
||||
if (!FileUtilities.createDir(cacheDir)) {
|
||||
throw new IOException("Failed to create the python cache directory at: " + cacheDir);
|
||||
}
|
||||
|
||||
File pythonSrcDestDir = new File(cacheDir, PYTHON_SRC);
|
||||
if (!FileUtilities.createDir(pythonSrcDestDir)) {
|
||||
throw new IOException(
|
||||
"Failed to create the " + PYTHON_SRC + " directory at: " + pythonSrcDestDir);
|
||||
}
|
||||
|
||||
File pythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File pythonSrcDir = new File(pythonModuleDir, PYTHON_SRC);
|
||||
if (!pythonSrcDir.exists()) {
|
||||
try {
|
||||
pythonSrcDir = Application.getModuleDataSubDirectory(pythonModuleDir.getName(),
|
||||
PYTHON_SRC).getFile(false);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new IOException("Failed to find the module's " + PYTHON_SRC + " directory");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
FileUtilities.copyDir(pythonSrcDir, pythonSrcDestDir, f -> f.getName().endsWith(".py"),
|
||||
monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException(
|
||||
"Failed to copy " + PYTHON_SRC + " files to: " + pythonSrcDestDir);
|
||||
}
|
||||
|
||||
System.setProperty("python.cachedir.skip", "false");
|
||||
System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
|
||||
System.setProperty("python.path", pythonSrcDestDir.getAbsolutePath());
|
||||
|
||||
return cacheDir;
|
||||
}
|
||||
}
|
BIN
Ghidra/Features/Python/src/main/resources/images/python.png
Normal file
BIN
Ghidra/Features/Python/src/main/resources/images/python.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
|
@ -0,0 +1,93 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
/**
|
||||
* Tests the Ghidra python interpreter's functionality.
|
||||
*/
|
||||
public class PythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private ByteArrayOutputStream out;
|
||||
private GhidraPythonInterpreter interpreter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
out = new ByteArrayOutputStream();
|
||||
interpreter = GhidraPythonInterpreter.get();
|
||||
interpreter.setOut(out);
|
||||
interpreter.setErr(out);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
out.reset();
|
||||
interpreter.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "push" method is working by executing a simple line of python.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonPush() {
|
||||
final String str = "hi";
|
||||
interpreter.push("print \"" + str + "\"", null);
|
||||
assertEquals(out.toString().trim(), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "execFile" method is working by executing a simple file of python.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonExecFile() {
|
||||
interpreter.execFile(new ResourceFile("ghidra_scripts/python_basics.py"), null);
|
||||
assertTrue(out.toString().contains("Snoopy"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that our sitecustomize.py modules gets loaded by testing the custom help function
|
||||
* that we install from there.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonSiteCustomize() {
|
||||
interpreter.push("help", null);
|
||||
assertTrue(out.toString().contains("Press 'F1'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that cleaning the interpreter invalidates it.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonCleanupInvalidation() {
|
||||
interpreter.cleanup();
|
||||
|
||||
try {
|
||||
interpreter.push("pass", null);
|
||||
fail("Push still worked after interpreter cleanup.");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// If everything worked, we should end up here.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
|
||||
/**
|
||||
* Tests the Python Plugin functionality.
|
||||
*/
|
||||
public class PythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private PythonPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
tool.addPlugin(PythonPlugin.class.getName());
|
||||
plugin = env.getPlugin(PythonPlugin.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that issuing a reset from the plugin resets the interpreter.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonPluginReset() {
|
||||
GhidraPythonInterpreter origInterpreter = plugin.getInterpreter();
|
||||
plugin.reset();
|
||||
GhidraPythonInterpreter newInterpreter = plugin.getInterpreter();
|
||||
assertNotSame(origInterpreter, newInterpreter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/* ###
|
||||
* 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.python;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Tests the Python script functionality.
|
||||
*/
|
||||
public class PythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private ConsoleService console;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
tool.addPlugin(ConsolePlugin.class.getName());
|
||||
console = tool.getService(ConsoleService.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Python scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonScript() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
String output = runPythonScript(Application.getModuleFile("Python", script));
|
||||
assertTrue(output.contains("Snoopy"));
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find python script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Python scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testPythonInterpreterGoneFromState() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
runPythonScript(Application.getModuleFile("Python", script), state);
|
||||
assertTrue(state.getEnvironmentVar(PythonScript.PYTHON_INTERPRETER) == null);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find python script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile) throws Exception {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
return runPythonScript(scriptFile, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script with the given initial state.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @param state The initial state of the script.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile, GhidraState state) throws Exception {
|
||||
|
||||
runSwing(() -> console.clearMessages());
|
||||
|
||||
PythonScriptProvider scriptProvider = new PythonScriptProvider();
|
||||
PrintWriter writer = new PrintWriter(new ByteArrayOutputStream());
|
||||
PythonScript script = (PythonScript) scriptProvider.getScriptInstance(scriptFile, writer);
|
||||
script.set(state, TaskMonitor.DUMMY, writer);
|
||||
script.run();
|
||||
|
||||
AtomicReference<String> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
String text = console.getText(0, console.getTextLength());
|
||||
ref.set(text);
|
||||
});
|
||||
|
||||
String text = ref.get();
|
||||
return text;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue