Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

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

View file

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

View file

@ -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>&nbsp;&nbsp;Clear the console and reset the interpreter</LI>
<LI><B>CTRL+I:</B>&nbsp;&nbsp;Interrupt the interpreter</LI>
<LI><B>(up):</B>&nbsp;&nbsp;Move backward in command stack</LI>
<LI><B>(down):</B>&nbsp;&nbsp;Move forward in command stack</LI>
<LI><B>TAB:</B>&nbsp;&nbsp;Show code completion window</LI>
</UL>
<P>
With the code completion window open:
<UL>
<LI><B>TAB:</B>&nbsp;&nbsp;Insert currently-selected code completion (if no completion selected, select the first available)</LI>
<LI><B>ENTER:</B>&nbsp;&nbsp;Insert selected completion (if any) and close the completion window</LI>
<LI><B>(up):</B>&nbsp;&nbsp;Select previous code completion</LI>
<LI><B>(down):</B>&nbsp;&nbsp;Select next code completion</LI>
<LI><B>ESC:</B>&nbsp;&nbsp;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>&nbsp;&nbsp;CTRL+C / CTRL+V</LI>
<LI><B>Linux:</B>&nbsp;&nbsp;CTRL+C / CTRL+V</LI>
<LI><B>OS X:</B>&nbsp;&nbsp;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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

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

View file

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

View file

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