GP-4659: Renaming "Python" module to "Jython"

This commit is contained in:
Ryan Kurtz
2024-06-03 11:29:56 -04:00
parent 9840eee937
commit fb1f725f5b
46 changed files with 285 additions and 284 deletions

View File

@@ -0,0 +1,4 @@
DATA SEARCH IGNORE DIR: jython-2.7.3
EXCLUDE FROM GHIDRA JAR: true
MODULE FILE LICENSE: lib/jython-standalone-2.7.3.jar Jython License

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.
*/
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply from: "$rootProject.projectDir/gradle/javadoc.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Features Jython'
def JYTHON = "org.python:jython-standalone:2.7.3"
def JYTHON_DIR = "jython-2.7.3"
configurations {
jython
}
dependencies {
api project(':Base')
jython JYTHON
api JYTHON
}
task jythonUnpack(type: Copy) {
description "Unpack Jython Lib"
// Without this, the copyTask will unzip the file to check for "up to date"
onlyIf {
!file("build/data/${JYTHON_DIR}").exists()
}
from zipTree(configurations.jython.singleFile)
include "Lib/**"
destinationDir file("build/data/${JYTHON_DIR}")
}
task jythonSrcCopy(type: Copy) {
description "Copy jython-src directory (for Feature Jython)"
from(file("jython-src"))
destinationDir file("build/data/jython-src")
}
// Ensure that Jython is usable in development xx
rootProject.prepDev.dependsOn jythonUnpack
jar.dependsOn jythonUnpack
jar.dependsOn jythonSrcCopy

View File

@@ -0,0 +1,8 @@
##VERSION: 2.0
##MODULE IP: Jython License
##MODULE IP: LGPL 2.1
Module.manifest||GHIDRA||||END|
data/jython.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/topics/Jython/interpreter.html||GHIDRA||||END|
src/main/resources/images/python.png||GHIDRA||||END|

View File

@@ -0,0 +1,21 @@
[Defaults]
color.fg.plugin.jython.syntax.class = color.palette.blue
color.fg.plugin.jython.syntax.code = color.palette.darkgreen
color.fg.plugin.jython.syntax.function = color.palette.green
color.fg.plugin.jython.syntax.instance = color.palette.purple
color.fg.plugin.jython.syntax.map = color.palette.steelblue
color.fg.plugin.jython.syntax.method = color.palette.teal
color.fg.plugin.jython.syntax.null = color.palette.red
color.fg.plugin.jython.syntax.number = color.palette.darkgray
color.fg.plugin.jython.syntax.package = color.palette.darkred
color.fg.plugin.jython.syntax.sequence = color.palette.saddlebrown
color.fg.plugin.jython.syntax.special = color.palette.darkgreen
icon.plugin.jython = python.png
[Dark Defaults]

View File

@@ -0,0 +1,31 @@
## ###
# 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.
##
# Adds a comment to a program.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from ghidra.program.model.address.Address import *
from ghidra.program.model.listing.CodeUnit import *
from ghidra.program.model.listing.Listing import *
minAddress = currentProgram.getMinAddress()
listing = currentProgram.getListing()
codeUnit = listing.getCodeUnitAt(minAddress)
codeUnit.setComment(codeUnit.PLATE_COMMENT, "AddCommentToProgramScript - This is an added comment!")

View File

@@ -0,0 +1,112 @@
## ###
# 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.
##
# An example of asking for user input.
# Note the ability to pre-populate values for some of these variables when AskScript.properties file exists.
# Also notice how the previous input is saved.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from ghidra.framework.model import DomainFile
from ghidra.framework.model import DomainFolder
from ghidra.program.model.address import Address
from ghidra.program.model.lang import LanguageCompilerSpecPair
from ghidra.program.model.listing import Program
from ghidra.util import Msg
from java.lang import IllegalArgumentException
from ghidra.util.exception import CancelledException
# The presence of the AskScript.properties file in the same location (as AskScript.java)
# allows for the following behavior:
# - GUI: if applicable, auto-populates the input field with the value in the
# .properties file (the first time that input field appears)
# - Headless: uses the value in the .properties file for the variable assigned to the
# corresponding askXxx() method in the GhidraScript.
try:
file1 = askFile("FILE", "Choose file:")
print "file was: " + str(file1)
directory1 = askDirectory("Directory", "Choose directory:")
print "directory was: " + str(directory1)
lang = askLanguage("Language Picker", "I want this one!")
print "language was: " + lang.toString()
domFolder = askProjectFolder("Please pick a domain folder!")
print "domFolder was: " + domFolder.getName()
int1 = askInt("integer 1", "enter integer 1")
int2 = askInt("integer 2", "enter integer 2")
print "int1 + int2 = " + str(int1 + int2)
long1 = askLong("long 1", "enter long 1")
long2 = askLong("long 2", "enter long 2")
print "long1 + long2 = " + str(long1 + long2)
address1 = askAddress("address 1", "enter address 1")
address2 = askAddress("address 2", "enter address 2")
print "address1 + address2 = " + address1.add(address2.getOffset()).toString()
#bytes = askBytes("bytes", "enter byte pattern")
#for b in bytes:
# print "b = " + str(b & 0xff)
try:
prog = askProgram("Please choose a program to open.")
print "Program picked: " + prog.getName()
finally:
if prog is not None:
prog.release(this)
domFile = askDomainFile("Which domain file would you like?")
print "Domain file: " + domFile.getName()
d1 = askDouble("double 1", "enter double 1")
d2 = askDouble("double 2", "enter double 2")
print "d1 + d2 = " + str(d1 + d2)
myStr = askString("String Specification", "Please type a string: ")
myOtherStr = askString("Another String Specification", "Please type another string: ", "replace me!")
print "You typed: " + myStr + " and " + myOtherStr
choice = askChoice("Choice", "Please choose one", [ "grumpy", "dopey", "sleepy", "doc", "bashful" ], "bashful")
print "Choice? " + choice
choices1 = askChoices("Choices 1", "Please choose one or more numbers.", [ 1, 2, 3, 4, 5, 6 ])
print "Choices 1: "
for intChoice in choices1:
print str(intChoice) + " "
print ""
choices2 = askChoices("Choices 2", "Please choose one or more of the following.",
[ 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 ], ["Part 1", "Part 2", "Part 3", "Part 4", "Part 5", "Part 6" ])
print "Choices 2: "
for intChoice in choices2:
print str(intChoice) + " "
print ""
yesOrNo = askYesNo("yes or no", "is this a yes/no question?")
print "Yes or No? " + str(yesOrNo)
except IllegalArgumentException as error:
Msg.warn(self, "Error during headless processing: " + error.toString())
except CancelledException:
print "Cancelled"

View File

@@ -0,0 +1,90 @@
## ###
# 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.
##
# Shows how to run a script on all of the programs within the current project.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
# NOTE: Script will only process unversioned and checked-out files.
#@category Examples.Python
from ghidra.app.script import GhidraState
from ghidra.framework.model import *
from ghidra.program.database import ProgramContentHandler
from ghidra.program.model.listing import Program
from ghidra.util.exception import CancelledException
from ghidra.util.exception import VersionException
from java.io import IOException
# The script referenced in the following line should be replaced with the script to be called
SUBSCRIPT_NAME = "AddCommentToProgramScript.java"
def recurseProjectFolder(domainFolder):
files = domainFolder.getFiles()
for domainFile in files:
processDomainFile(domainFile)
folders = domainFolder.getFolders()
for folder in folders:
recurseProjectFolder(folder)
def processDomainFile(domainFile):
if not ProgramContentHandler.PROGRAM_CONTENT_TYPE == domainFile.getContentType():
return # skip non-Program files
if domainFile.isVersioned() and not domainFile.isCheckedOut():
println("WARNING! Skipping versioned file - not checked-out: " + domainFile.getPathname())
program = None
consumer = java.lang.Object()
try:
program = domainFile.getDomainObject(consumer, True, False, monitor)
processProgram(program)
except VersionException:
println("ERROR! Failed to process file due to upgrade issue: " + domainFile.getPathname())
finally:
if program is not None:
program.release(consumer)
def processProgram(program):
"""Do you program work here """
println("Processing: " + program.getDomainFile().getPathname())
monitor.setMessage("Processing: " + program.getDomainFile().getName())
id = program.startTransaction("Batch Script Transaction")
try:
newState = GhidraState(state.getTool(), state.getProject(), program, None, None, None)
runScript(SUBSCRIPT_NAME, newState)
except Exception:
printerr("ERROR! Exception occurred while processing file: " + program.getDomainFile().getPathname())
printerr(" " + Exception.getMessage())
e.printStackTrace()
finally:
program.endTransaction(id, True)
# ...save any changes
program.save("Changes made by script: " + SUBSCRIPT_NAME, monitor)
if currentProgram is not None:
popup("This script should be run from a tool with no open programs")
exit()
project = state.getProject()
projectData = project.getProjectData()
rootFolder = projectData.getRootFolder()
recurseProjectFolder(rootFolder)

View File

@@ -0,0 +1,24 @@
## ###
# 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.
##
# Example of a script calling another script.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
runScript("HelloWorldScript.java")
runScript("HelloWorldPopupScript.java")

View File

@@ -0,0 +1,36 @@
## ###
# 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.
##
# Example of a script prompting the user for a data type.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from ghidra.app.util.datatype import DataTypeSelectionDialog
from ghidra.framework.plugintool import PluginTool
from ghidra.program.model.data import DataType
from ghidra.program.model.data import DataTypeManager
from ghidra.util.data.DataTypeParser import AllowedDataTypes
tool = state.getTool()
dtm = currentProgram.getDataTypeManager()
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
tool.showDialog(selectionDialog)
dataType = selectionDialog.getUserChosenDataType()
if dataType is not None:
print "Chosen data type: " + str(dataType)

View File

@@ -0,0 +1,48 @@
## ###
# 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.
##
# An example of how to color the listing background
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from ghidra.app.plugin.core.colorizer import ColorizingService
from ghidra.app.script import GhidraScript
from ghidra.program.model.address import Address
from ghidra.program.model.address import AddressSet
from java.awt import Color
service = state.getTool().getService(ColorizingService)
if service is None:
print "Can't find ColorizingService service"
if currentSelection is not None:
service.setBackgroundColor(currentSelection, Color(255, 200, 200))
elif currentAddress is not None:
service.setBackgroundColor(currentAddress, currentAddress, Color(255, 200, 200))
else:
print "No selection or current address to color"
anotherAddress = currentAddress.add(10)
setBackgroundColor(anotherAddress, Color.YELLOW)
# create an address set with values you want to change
addresses = AddressSet()
addresses.add(currentAddress.add(10))
addresses.add(currentAddress.add(11))
addresses.add(currentAddress.add(12))
setBackgroundColor(addresses, Color(100, 100, 200))

View File

@@ -0,0 +1,43 @@
## ###
# 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.
##
# An example using the Python string formatting.
# See FormatExampleScript.java for examples of using the printf() method.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from time import *
import java.util.Calendar
print "The %s jumped over the %s" % ("cow", "moon")
print "The %s jumped over the %s " % ("cow", "moon") + strftime("%X")
print "The %s jumped over the %s - timestamp: %s" % ("cow", "moon", strftime("%c %Z"))
print "The %s jumped over the %s at %s on %s" % ("cow", "moon", strftime("%I:%M%p"), strftime("%A, %b %d"))
print "Padding: %03d" % (1)
print "Hex: 0x%x" % (10)
print "Left-justified: %-10d" % (1)
print "Right-justified: %10d" % (1)
print "String fill: '%10s'" % ("Fill")
print "String fill, left justified: '%-10s'" % ("Fill")

View File

@@ -0,0 +1,61 @@
## ###
# 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.
##
# Imports a text file containing symbol definitions, with a maximum of one symbol defined per line. Each symbol definition is in the form of "symbol_name address function_or_label", where "symbol_name" is the name of the symbol, "address" is the address of the symbol in one of the forms listed below, and "function_or_label" is either "f" or "l", with "f" indicating that a function is to be created and "l" indicating that a label is to be created.
# Address formats are the same as those that can be used with the "Go to address" function. For example:
# - 1234abcd
# - 0x1234abcd
# - ADDRESS_SPACE:1234abcd
# - ADDRESS_SPACE:0x1234abcd
# - MEMORY_REGION:1234abcd
# - MEMORY_REGION:0x1234abcd
# Omitting the address space or memory region specifier from the address will result in the function or label being created in the default address space.
# @author unkown; edited by matedealer <git@matedealer.de>
# @category Data
#
from ghidra.program.model.symbol.SourceType import *
import string
functionManager = currentProgram.getFunctionManager()
f = askFile("Give me a file to open", "Go baby go!")
for line in file(f.absolutePath): # note, cannot use open(), since that is in GhidraScript
pieces = line.split()
name = pieces[0]
address = toAddr(pieces[1])
try:
function_or_label = pieces[2]
except IndexError:
function_or_label = "l"
if function_or_label == "f":
func = functionManager.getFunctionAt(address)
if func is not None:
old_name = func.getName()
func.setName(name, USER_DEFINED)
print("Renamed function {} to {} at address {}".format(old_name, name, address))
else:
func = createFunction(address, name)
print("Created function {} at address {}".format(name, address))
else:
print("Created label {} at address {}".format(name, address))
createLabel(address, name, False)

View File

@@ -0,0 +1,20 @@
## ###
# 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.
##
# Prints out all the functions in the program that have a non-zero stack purge size
for func in currentProgram.getFunctionManager().getFunctions(currentProgram.evaluateAddress("0"), 1):
if func.getStackPurgeSize() != 0:
print "Function", func, "at", func.getEntryPoint(), "has nonzero purge size", func.getStackPurgeSize()

View File

@@ -0,0 +1,36 @@
## ###
# 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.
##
# Writes properties to the tool.
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
# use only. Please run the Java version in a production environment.
#@category Examples.Python
from ghidra.framework.options import Options
from ghidra.framework.plugintool import PluginTool
tool = state.getTool()
options = tool.getOptions("name of my script")
fooString = options.getString("foo", None)
if fooString is not None : #does not exist in tool options
fooString = askString("enter foo", "what value for foo:")
if fooString is not None :
options.setString("foo", fooString)
popup(fooString)

View File

@@ -0,0 +1,33 @@
## ###
# 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.
##
# Example of being imported by a Ghidra Python script/module
# @category: Examples.Python
# The following line will fail if this module is imported from external_module_caller.py,
# because only the script that gets directly launched by Ghidra inherits fields and methods
# from the GhidraScript/FlatProgramAPI.
try:
print currentProgram.getName()
except NameError:
print "Failed to get the program name"
# The Python module that Ghidra directly launches is always called __main__. If we import
# everything from that module, this module will behave as if Ghidra directly launched it.
from __main__ import *
# The below method call should now work
print currentProgram.getName()

View File

@@ -0,0 +1,21 @@
## ###
# 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.
##
# Example of importing an external Ghidra Python module
# @category: Examples.Python
# Import the external module that wants to access the Ghidra scripting API.
# NOTE: see external_module_callee.py for additional tips.
import external_module_callee

View File

@@ -0,0 +1,77 @@
## ###
# 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.
##
# Examples of basic Ghidra scripting in Python
# @category: Examples.Python
# Get info about the current program
print
print "Program Info:"
program_name = currentProgram.getName()
creation_date = currentProgram.getCreationDate()
language_id = currentProgram.getLanguageID()
compiler_spec_id = currentProgram.getCompilerSpec().getCompilerSpecID()
print "%s: %s_%s (%s)\n" % (program_name, language_id, compiler_spec_id, creation_date)
# Get info about the current program's memory layout
print "Memory layout:"
print "Imagebase: " + hex(currentProgram.getImageBase().getOffset())
for block in getMemoryBlocks():
start = block.getStart().getOffset()
end = block.getEnd().getOffset()
print "%s [start: 0x%x, end: 0x%x]" % (block.getName(), start, end)
print
# Get the current program's function names
function = getFirstFunction()
while function is not None:
print function.getName()
function = getFunctionAfter(function)
print
# Get the address of the current program's current location
print "Current location: " + hex(currentLocation.getAddress().getOffset())
# Get some user input
val = askString("Hello", "Please enter a value")
print val
# Output to a popup window
popup(val)
# Add a comment to the current program
minAddress = currentProgram.getMinAddress()
listing = currentProgram.getListing()
codeUnit = listing.getCodeUnitAt(minAddress)
codeUnit.setComment(codeUnit.PLATE_COMMENT, "This is an added comment!")
# Get a data type from the user
from ghidra.app.util.datatype import DataTypeSelectionDialog
from ghidra.util.data.DataTypeParser import AllowedDataTypes
tool = state.getTool()
dtm = currentProgram.getDataTypeManager()
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
tool.showDialog(selectionDialog)
dataType = selectionDialog.getUserChosenDataType()
if dataType != None: print "Chosen data type: " + str(dataType)
# Report progress to the GUI. Do this in all script loops!
import time
monitor.initialize(10)
for i in range(10):
monitor.checkCanceled() # check to see if the user clicked cancel
time.sleep(1) # pause a bit so we can see progress
monitor.incrementProgress(1) # update the progress
monitor.setMessage("Working on " + str(i)) # update the status message

View File

@@ -0,0 +1,53 @@
## ###
# 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.
##
# Examples of Jython-specific functionality
# @category: Examples.Python
# Using Java data structures from Jython
python_list = [1, 2, 3]
java_list = java.util.LinkedList(java.util.Arrays.asList(1, 2, 3))
print str(type(python_list))
print str(type(java_list))
# Importing Java packages for simpler Java calls
from java.util import LinkedList, Arrays
python_list = [1, 2, 3]
java_list = LinkedList(Arrays.asList(1, 2, 3))
print str(type(python_list))
print str(type(java_list))
# Python adds helpful syntax to Java data structures
print python_list[0]
print java_list[0] # can't normally do this in java
print java_list[0:2] # can't normally do this in java
# Iterate over Java collection the Python way
for entry in java_list:
print entry
# "in" keyword compatibility
print str(3 in java_list)
# Create GUI with Java Swing
from javax.swing import JFrame
frame = JFrame() # don't call constructor with "new"
frame.setSize(400,400)
frame.setLocation(200, 200)
frame.setTitle("Jython JFrame")
frame.setVisible(True)
# Use JavaBean properties in constructor with keyword arguments!
JFrame(title="Super Jython JFrame", size=(400,400), visible=True)

View File

@@ -0,0 +1,117 @@
## ###
# 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.
##
# Examples of basic Python
# @category: Examples.Python
# Python data types
my_int = 32
print my_int
print hex(my_int)
my_bool = True
print my_bool
print not my_bool
my_string = 'this is a string'
print my_string
print my_string[:4]
print my_string[-5:]
print type(my_string)
my_list = ["a", 2, 5.3, my_string]
print my_list
print my_list[1]
print my_list[1:2]
print my_list + [1, 2, 3]
print type(my_list)
my_tuple = (1, 2, 3)
print my_tuple
print my_tuple + (4,)
print type(my_tuple)
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
print my_dictionary["key3"]
print type(my_dictionary)
my_null = None
print my_null
print type(my_null)
# Python conditionals
if len(my_string) == 16:
print "length of my_string is 16!"
if 4 not in my_list:
print "4 is not in my_list!"
if type(my_dictionary) == type(dict):
print "my_dictionary is a dictionary!"
if my_null is None and 2 + 2 == 4:
print "my_null is None and 2 + 2 == 4!"
# Python loops
for i in range(1, 10):
print i
for letter in "word":
print letter
for element in [100, 200, 300]:
print element
for key in my_dictionary:
print "%s:%s" % (key, my_dictionary[key])
i = 5
while i < 8:
print i
i += 1
# Python functions
def factorial(n):
if n == 0:
return 1
return n * factorial(n-1)
i = 4
print str(i) + "! = " + str(factorial(4))
# Python exceptions
def error_function():
raise IOError("An IO error occurred!")
try:
error_function()
print "I won't print"
except IOError as e:
print e.message
# Python class
class Employee:
def __init__(self, id, name):
self.id = id
self.name = name
def getId(self):
return self.id
def getName(self):
return self.name
e = Employee(5555, "Snoopy")
print e.getName()

View File

@@ -0,0 +1,190 @@
## ###
# 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.
##
"""
Ties the Ghidra documentation into the builtin Python help.
"""
import __builtin__
import java
import re
import json
import os
import zipfile
from ghidra.framework import Application
from ghidra.util import SystemUtilities
class _Helper:
def __init__(self):
self.orig_help = __builtin__.help
if SystemUtilities.isInHeadlessMode():
# ./pythonRun scenario
self.msg = "\nExample workflow:\n"
self.msg += " # Import headless analyzer\n"
self.msg += " from ghidra.app.util.headless import HeadlessAnalyzer\n\n"
self.msg += " # View HeadlessAnalyzer API\n"
self.msg += " help(HeadlessAnalyzer)\n\n"
self.msg += " # Get a HeadlessAnalyzer instance\n"
self.msg += " headless = HeadlessAnalyzer.getInstance()\n\n"
self.msg += " # Get headless options\n"
self.msg += " options = headless.getOptions()\n\n"
self.msg += " # View HeadlessOptions API and set options accordingly\n"
self.msg += " help(options)\n\n"
self.msg += " # View processLocal method API\n"
self.msg += " help(headless.processLocal)\n\n"
self.msg += " # Perform headless processing\n"
self.msg += " headless.processLocal(...)\n\n"
else:
# PythonPlugin scenario
self.msg = "Press 'F1' for usage instructions"
def __call__(self, param=None):
def get_class_and_method(param):
if param is None and not SystemUtilities.isInHeadlessMode():
# Enable help() in PythonPlugin scenario to show help for GhidraScript
return "ghidra.app.script.GhidraScript", None
class_name = None
method_name = None
if type(param) in [type(1), type(1j), type(1L), type(1.0), type(None), type(True), type([]), type({}), type(()), type({1})]:
# These are instances of builtin types, so skip
pass
elif type(param) == type(str):
# These are builtin Python types, so skip
pass
elif type(param) == type(str.split):
# These are python functions, so skip
pass
elif type(param) == type(java):
# These are java packages, which we don't don't document, so skip
pass
elif type(param) == type(java.lang.Object):
# This is a java class, so extract its class name
match = re.search("'(.*)'", str(param))
if match is not None:
class_name = match.group(1)
elif type(param) == type(java.lang.Object().toString):
# This is a java method, so extract its class name and method name
tokens = str(param).split(" ")[2].split(".")
class_name = ".".join(tokens[:-1])
method_name = tokens[-1]
else:
# Assuming this is a java object, so extract its class name
match = re.search("'(.*)'", str(type(param)))
if match is not None:
class_name = match.group(1)
return class_name, method_name
def get_jsondoc(class_name):
jsondoc = None
try:
root = Application.getApplicationRootDirectory().getFile(False).getParentFile().getAbsolutePath()
javadoc_zip_name = "GhidraAPI_javadoc.zip"
if SystemUtilities.isInDevelopmentMode():
javadoc_zip = root + "/build/tmp/" + javadoc_zip_name
else:
javadoc_zip = root + "/docs/" + javadoc_zip_name
if os.path.exists(javadoc_zip):
json_path = "api/" + class_name.replace('.', '/') + '.json'
with zipfile.ZipFile(javadoc_zip, "r").open(json_path) as f:
jsondoc = json.load(f)
except (IOError, KeyError) as e:
pass
return jsondoc
def format_class(cls):
sig = "class " + cls["name"] + "\n"
if "extends" in cls:
sig += " extends " + cls["extends"] + "\n"
implements = ""
for interface in cls["implements"]:
if len(implements) > 0:
implements += ", "
implements += interface
if len(implements) > 0:
sig += " implements " + implements + " \n"
sig += "\n" + cls["comment"]
return sig
def format_field(field):
sig = "%s %s" % (field["type_long"], field["name"])
if field["static"]:
sig = "static " + sig
if field["constant_value"]:
sig += " = " + field["constant_value"]
sig += "\n"
desc = " %s\n" % (field["comment"]) if len(field["comment"]) > 0 else ""
return sig + desc
def format_method(method):
paramsig = ""
args = ""
for param in method["params"]:
if len(paramsig) > 0:
paramsig += ", "
paramsig += "%s %s" % (param["type_short"], param["name"])
args += " @param %s (%s): %s\n" % (param["name"], param["type_long"], param["comment"])
throws = ""
for exception in method["throws"]:
throws += " @throws %s: %s\n" % (exception["type_short"], exception["comment"])
sig = "%s %s(%s)\n" % (method["return"]["type_short"], method["name"], paramsig)
if method["static"]:
sig = "static " + sig
desc = " %s\n\n" % (method["comment"]) if len(method["comment"]) > 0 else ""
ret = ""
if method["return"]["type_short"] != "void":
ret = " @return %s: %s\n" % (method["return"]["type_long"], method["return"]["comment"])
return sig + desc + args + ret + throws
class_name, method_name = get_class_and_method(param)
if class_name is None:
self.orig_help(param)
else:
try_again = True
while try_again:
try_again = False
print "Searching API for " + class_name + ("" if method_name is None else "." + method_name + "()") + "..."
jsondoc = get_jsondoc(class_name)
if jsondoc is None:
print "No API found for " + class_name
elif method_name is None:
print "#####################################################"
print format_class(jsondoc)
print "#####################################################\n"
for field in jsondoc["fields"]:
print format_field(field)
print "-----------------------------------------------------"
for method in jsondoc["methods"]:
print format_method(method)
print "-----------------------------------------------------"
else:
found_method = False
for method in jsondoc["methods"]:
if method["name"] == method_name:
print "-----------------------------------------------------"
print format_method(method)
print "-----------------------------------------------------"
found_method = True
if not found_method:
# The method may be inherited, so check for a super class and try again
if "extends" in jsondoc:
class_name = jsondoc["extends"]
try_again = True
def __repr__(self):
return self.msg
__builtin__.help = _Helper()

View File

@@ -0,0 +1,530 @@
## ###
# IP: LGPL 2.1
##
"""
Provides a variety of introspective-type support functions for
things like call tips and command auto completion.
NOTE: this file is a modification of Patrick O'Brien's version 1.62
"""
from __future__ import nested_scopes
import cStringIO
import inspect
import sys
import tokenize
import types
import __main__
from ghidra.jython import JythonCodeCompletionFactory
import java # needed for java.lang.Class
try:
True
except NameError:
True = 1==1
False = 1==0
#from java.lang.System.out import println
def getAutoCompleteList(command='', locals=None, includeMagic=1,
includeSingle=1, includeDouble=1):
"""Return list of auto-completion tuples for command.
First entry is the possible completions, and second entry is the
of actual string that should be added to do the completion.
The list of options will be based on the locals namespace."""
attributes = []
object = None
# Get the proper chunk of code from the command.
#root = getRoot(command, terminator='.')
# and get the part of the completion we should filter on
(root, filter) = getRootAndFilter(command, terminator='.')
if root:
jump_past_period = 1
else:
jump_past_period = 0
#println("root='" + root + "'")
#println("filter='" + filter + "'")
if not root:
# top-level?
attributes = locals
else:
try:
if locals is not None:
object = eval(root, locals)
else:
object = eval(root)
except:
#print "could not eval(", root, "):", sys.exc_info()[0]
pass
else:
attributes = getAttributeNames(object, includeMagic,
includeSingle, includeDouble)
completion_list = []
for attribute in attributes:
if attribute.lower().startswith(filter.lower()):
try:
if object is not None:
pyObj = getattr(object, attribute)
else:
pyObj = locals[attribute]
completion_list.append(JythonCodeCompletionFactory.
newCodeCompletion(attribute,
attribute,
pyObj,
filter))
except:
# hmm, problem evaluating? Examples of this include
# inner classes, e.g. access$0, which aren't valid Python
# anyway
pass
completion_list.sort(compare_completions)
return completion_list
def compare_completions(comp1, comp2):
return cmp(comp1.description, comp2.description)
def getAttributeNames(object, includeMagic=1, includeSingle=1,
includeDouble=1):
"""Return list of unique attributes, including inherited, for object."""
attributes = []
dict = {}
if not hasattrAlwaysReturnsTrue(object):
# Add some attributes that don't always get picked up. If
# they don't apply, they'll get filtered out at the end
attributes += ['__bases__', '__class__', '__dict__', '__name__',
'func_closure', 'func_code', 'func_defaults',
'func_dict', 'func_doc', 'func_globals', 'func_name']
if includeMagic:
try: attributes += object._getAttributeNames()
except: pass
# Get all attribute names.
attrdict = getAllAttributeNames(object)
for attrlist in attrdict.values():
attributes += attrlist
# Remove duplicates from the attribute list.
for item in attributes:
dict[item] = None
attributes = dict.keys()
attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
if not includeSingle:
attributes = filter(lambda item: item[0]!='_' \
or item[1]=='_', attributes)
if not includeDouble:
attributes = filter(lambda item: item[:2]!='__', attributes)
#print "attributes currently", attributes
# Make sure we haven't picked up any bogus attributes somehow.
#attributes = [attribute for attribute in attributes \
# if hasattr(object, attribute)]
retval = []
for attribute in attributes:
#print "checking", attribute
try:
if hasattr(object, attribute):
retval += [attribute]
except:
# yes Virginia, hasattr() can fail in Jython 2.2 because trying
# to get __doc__ on a builtin class results in NullPointerException
pass
return retval
def hasattrAlwaysReturnsTrue(object):
return hasattr(object, 'bogu5_123_aTTri8ute')
def getAllAttributeNames(object):
"""Return dict of all attributes, including inherited, for an object.
Recursively walk through a class and all base classes.
"""
attrdict = {} # (object, technique, count): [list of attributes]
# !!!
# Do Not use hasattr() as a test anywhere in this function,
# because it is unreliable with the remote objects: xmlrpc, soap, etc.
# They always return true for hasattr().
# !!!
try:
# Yes, this can fail if object is an instance of a class with
# __str__ (or __repr__) having a bug or raising an
# exception. :-(
key = str(object)
except:
key = 'anonymous'
# Wake up sleepy object - a hack for ZODB objects in "ghost" state.
try:
wakeupcall = dir(object)
del wakeupcall
except:
pass
# Get attributes available through the normal convention
try:
attributes = dir(object)
attrdict[(key, 'dir', len(attributes))] = attributes
except:
# sadly, this fails for PyReflectedFunctions
pass
# Get attributes rom the object's dictionary, if it has one.
try:
attributes = object.__dict__.keys()
attributes.sort()
except: # Must catch all because object might have __getattr__.
pass
else:
attrdict[(key, '__dict__', len(attributes))] = attributes
# For a class instance, get the attributes for the class.
try:
klass = object.__class__
except: # Must catch all because object might have __getattr__.
pass
else:
if klass is object:
# Break a circular reference. This happens with extension
# classes.
#print "breaking circular reference to self"
pass
# this extra check added for Jython 2.2.1 to break circular recursion
elif klass is not java.lang.Class:
# print "calling update from", object, "with", klass
attrdict.update(getAllAttributeNames(klass))
# Also get attributes from any and all parent classes.
try:
bases = object.__bases__
except: # Must catch all because object might have __getattr__.
pass
else:
if isinstance(bases, types.TupleType):
# needed for Jython 2.2?
halt_type = type(types.TypeType)
for base in bases:
if type(base) is types.TypeType \
or type(base) is halt_type:
# Break a circular reference. Happens in Python 2.2.
#print "breaking TypeType circular reference"
pass
else:
# print "calling update (better not be 'type') with", base
attrdict.update(getAllAttributeNames(base))
return attrdict
def getCallTip(command='', locals=None):
"""For a command, return a tuple of object name, argspec, tip text.
The call tip information will be based on the locals namespace."""
calltip = ('', '', '') # object name, argspec, tip text.
# Get the proper chunk of code from the command.
root = getRoot(command, terminator='(')
try:
if locals is not None:
object = eval(root, locals)
else:
object = eval(root)
except:
#print "could not eval(", root, "):", sys.exc_info()[0]
return calltip
name = ''
object, dropSelf = getBaseObject(object)
try:
name = object.__name__
except AttributeError:
pass
tip1 = ''
argspec = ''
if inspect.isbuiltin(object):
# Builtin function don't have an argspec that we can get.
pass
elif inspect.isfunction(object):
# tip1 is a string like: "getCallTip(command='', locals=None)"
argspec = apply(inspect.formatargspec, inspect.getargspec(object))
if dropSelf:
# Thh first parameter to a method is a reference to an
# instance, usually coded as "self", and is usually passed
# automatically by Python; therefore we want to drop it.
temp = argspec.split(',')
if len(temp) == 1: # No other arguments.
argspec = '()'
else: # Drop the first argument.
argspec = '(' + ','.join(temp[1:]).lstrip()
tip1 = name + argspec
doc = ''
if callable(object):
try:
doc = inspect.getdoc(object)
except:
# Jython 2.2a1 could throw and exception with getdoc()
pass
if doc:
# tip2 is the first separated line of the docstring, like:
# "Return call tip text for a command."
# tip3 is the rest of the docstring, like:
# "The call tip information will be based on ... <snip>
firstline = doc.split('\n')[0].lstrip()
if tip1 == firstline:
tip1 = ''
else:
tip1 += '\n\n'
docpieces = doc.split('\n\n')
tip2 = docpieces[0]
tip3 = '\n\n'.join(docpieces[1:])
tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
else:
tip = tip1
calltip = (name, argspec[1:-1], tip.strip())
return calltip
def getRoot(command, terminator=None):
"""Return the rightmost root portion of an arbitrary Python command.
Return only the root portion that can be eval()ed without side
effect. The command would normally terminate with a '(' or
'.'. The terminator and anything after the terminator will be
dropped."""
command = command.split('\n')[-1]
if command.startswith(sys.ps2):
command = command[len(sys.ps2):]
command = command.lstrip()
command = rtrimTerminus(command, terminator)
tokens = getTokens(command)
if not tokens:
return ''
if tokens[-1][0] is tokenize.ENDMARKER:
# Remove the end marker.
del tokens[-1]
if not tokens:
return ''
if terminator == '.' and \
(tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
# Trap decimals in numbers, versus the dot operator
return ''
else:
# Strip off the terminator.
if terminator and command.endswith(terminator):
size = 0 - len(terminator)
command = command[:size]
command = command.rstrip()
tokens = getTokens(command)
tokens.reverse()
line = ''
start = None
prefix = ''
laststring = '.'
emptyTypes = ('[]', '()', '{}')
for token in tokens:
tokentype = token[0]
tokenstring = token[1]
line = token[4]
if tokentype is tokenize.ENDMARKER:
continue
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
and laststring != '.':
# We've reached something that's not part of the root.
if prefix and line[token[3][1]] != ' ':
# If it doesn't have a space after it, remove the prefix.
prefix = ''
break
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
or (tokentype is tokenize.OP and tokenstring == '.'):
if prefix:
# The prefix isn't valid because it comes after a dot.
prefix = ''
break
else:
# start represents the last known good point in the line
start = token[2][1]
elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
# Remember, we're working backwards.
# So prefix += tokenstring would be wrong.
if prefix in emptyTypes and tokenstring in ('[({'):
# We've already got an empty type identified so now we
# are in a nested situation and we can break out with
# what we've got
break
else:
prefix = tokenstring = prefix
else:
# We've reached something that's not part of the root
break
laststring= tokenstring
if start is None:
start = len(line)
root = line[start:]
if prefix in emptyTypes:
# Empty types are safe to be eval()'d and introspected.
root = prefix + root
return root
def getRootAndFilter(command, terminator=None):
"""Return the rightmost root portion of an arbitrary Python command.
Also returns the filter, which is the fragment after the root and
terminator.
Return only the root portion that can be eval()ed without side
effect. The command would normally terminate with a '(' or
'.'. The terminator and anything after the terminator will be
dropped."""
command = command.split('\n')[-1]
if command.startswith(sys.ps2):
command = command[len(sys.ps2):]
command = command.lstrip()
tokens = getTokens(command)
if not tokens:
return ('', '')
tokens.reverse()
root = ""
filter = ""
terminator_seen = False
# initialize matching bracket/paren sets
op_mate = {'}': '{',
']': '[',
')': '('}
op_stack = []
while len(tokens) > 0:
# work backwards through the tokens, starting at the end of the string
token = tokens[0]
del tokens[0]
tokentype = token[0]
tokenstring = token[1]
line = token[4]
if tokentype is tokenize.ENDMARKER:
#println("Hit end marker; continuing")
continue
if not terminator_seen:
if not filter and tokentype in (tokenize.NAME, tokenize.STRING):
# okay, we think we've found our filter string
filter = tokenstring
#println("hit filter string '" + filter + "'")
elif tokenstring == terminator:
# hooray, our terminator!
terminator_seen = True
else:
# either we found another token before our already-set
# filter string, or we just found a bad token
#println("No valid tokens found after terminator (at '" +
# tokenstring + "'")
break
else:
# we've seen the terminator -- continue adding valid tokens
# until we hit a token that stops us
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER):
root = tokenstring + root
#println("Added to root: '" + root + "'")
elif tokentype is tokenize.OP:
if tokenstring in op_mate.keys():
# found a closing bracket/paren
op_stack.append(tokenstring)
elif tokenstring in op_mate.values():
if len(op_stack) < 1 or \
tokenstring != op_mate[op_stack.pop()]:
# uh-oh, non-matching brackets/parens!
break
elif len(op_stack) > 0:
# weird tokens are okay inside brackets/parens
pass
elif tokenstring == '.':
# dots are okay too!
pass
else:
break
root = tokenstring + root
#println("Added to root: '" + root + "'")
else:
# hit a terminating token
break
return (root, filter)
def getTokens(command):
"""Return list of token tuples for command."""
command = str(command) # In case the command is unicode, which fails.
f = cStringIO.StringIO(command)
# tokens is a list of token tuples, each looking like:
# (type, string, (srow, scol), (erow, ecol), line)
tokens = []
# Can't use list comprehension:
# tokens = [token for token in tokenize.generate_tokens(f.readline)]
# because of need to append as much as possible before TokenError.
try:
# This works with Python 2.1.3 (with nested_scopes)
def eater(*args):
tokens.append(args)
tokenize.tokenize_loop(f.readline, eater)
except tokenize.TokenError:
# This is due to a premature EOF, which we expect since we are
# feeding in fragments of Python code
pass
return tokens
def rtrimTerminus(command, terminator=None):
"""Return command minus anything that follows the final terminator."""
if terminator:
pieces = command.split(terminator)
if len(pieces) > 1:
command = terminator.join(pieces[:-1]) + terminator
return command
def getBaseObject(object):
"""Return base object and dropSelf indicator for an object."""
if inspect.isbuiltin(object):
# Builtin function don't have an argspec that we can get.
dropSelf = 0
elif inspect.ismethod(object):
# Get the function from the object otherwise
# inspect.getargspec() complains that the object isn't a
# Python funciton.
try:
if object.im_self is None:
# This is an unbound method so we do not drop self
# from the argpec, sinc ean instance must be passed
# as the first arg.
dropSelf = 0
else:
dropSelf = 1
object = object.im_func
except AttributeError:
dropSelf = 0
elif inspect.isclass(object):
# Get the __init__ method function for the class
constructor = getConstructor(object)
if constructor is not None:
object = constructor
dropSelf = 1
else:
dropSelf = 0
elif callable(object):
# Get the __call__ method instead
try:
call_method = object.__call__.im_func
if call_method.__name__ == '__call__':
# unbound jython method end up here, example: string.index(
dropSelf = 0
else:
object = call_method
dropSelf = 1
except AttributeError:
# unbound python methods end up here
dropSelf = 0
else:
dropSelf = 0
return object, dropSelf
def getConstructor(object):
"""Return constructor for class object, or None if there isn't one."""
try:
return object.__init__.im_func
except AttributeError:
for base in object.__bases__:
constructor = getConstructor(base)
if constructor is not None:
return constructor
return None

View File

@@ -0,0 +1,237 @@
## ###
# IP: LGPL 2.1
##
"""Extend introspect.py for Java based Jython classes."""
from introspect import *
import string
import __builtin__
import java # needed for java.lang.Class
import org # for org.python.core
import ghidra # for JythonCodeCompletionFactory
__author__ = "Don Coleman <dcoleman@chariotsolutions.com>"
#def getAutoCompleteList(command='', locals=None, includeMagic=1,
# includeSingle=1, includeDouble=1):
# """Return list of auto-completion options for command.
#
# The list of options will be based on the locals namespace."""
# attributes = []
# # Get the proper chunk of code from the command.
# root = getRoot(command, terminator='.')
# try:
# if locals is not None:
# object = eval(root, locals)
# else:
# object = eval(root)
# except:
# #print "could not eval(", root, "):", sys.exc_info()[0]
# return attributes
#
# if ispython(object):
# # use existing code
# attributes = getAttributeNames(object, includeMagic, includeSingle,
# includeDouble)
# else:
# methods = methodsOf(object.__class__)
# attributes = [eachMethod.__name__ for eachMethod in methods]
#
# return attributes
#
#def methodsOf(clazz):
# """Return a list of all the methods in a class"""
# classMembers = vars(clazz).values()
# methods = [eachMember for eachMember in classMembers
# if callable(eachMember)]
# for eachBase in clazz.__bases__:
# methods.extend(methodsOf(eachBase))
# return methods
def getCallTipJava(command='', locals=None):
"""For a command, return a tuple of object name, argspec, tip text.
The call tip information will be based on the locals namespace."""
calltip = ('', '', '') # object name, argspec, tip text.
# Get the proper chunk of code from the command.
(root, filter) = getRootAndFilter(command, terminator='(')
#java.lang.System.out.println("root=" + root)
try:
if locals is not None:
object = eval(root, locals)
else:
object = eval(root)
except:
#java.lang.System.err.println("could not eval(" + root + "):" +
# str(sys.exc_info()[0]))
return calltip
if ispython(object):
# Patrick's code handles Python code
# TODO fix in future because getCallTip runs eval() again
#java.lang.System.out.println("is a Python object")
calltip = getCallTip(command, locals)
if not calltip[1] and not calltip[2]:
# either it's a pure Java object, or we didn't get much from Python's
# getCallTip
name = ''
try:
name = object.__name__
except AttributeError:
pass
tipList = []
argspec = '' # not using argspec for Java
# if inspect.isbuiltin(object):
# # inspect.isbuiltin() fails for Jython
# # Can we get the argspec for Jython builtins? We can't in Python.
# # YES!
# print "is a builtin"
# pass
# elif inspect.isclass(object):
if inspect.isclass(object):
# get the constructor(s)
# TODO consider getting modifiers since Jython can access
# private methods
#java.lang.System.out.println("is a class")
try:
# this will likely fail for pure Python classes
constructors = object.getConstructors()
for constructor in constructors:
paramList = []
paramTypes = constructor.getParameterTypes()
# paramTypes is an array of classes; we need Strings
# TODO consider list comprehension
for param in paramTypes:
# TODO translate [B to byte[], C to char[], etc.
paramList.append(param.__name__)
paramString = string.join(paramList, ', ')
tip = "%s(%s)" % (constructor.name, paramString)
tipList.append(tip)
if len(constructors) == 1:
plural = ""
else:
plural = "s"
name = "Constructor" + plural + " for " + name + ":"
except:
pass
# if callable(object):
# # some Python types are function names as well, like
# # type() and file()
# argspec = str(object.__call__)
# # these don't seem to be very accurate
# if hasattr(object.__call__, "maxargs"):
# tipList.append("maxargs?: " +
# str(object.__call__.maxargs))
# if hasattr(object.__call__, "minargs"):
# tipList.append("minargs?: " +
# str(object.__call__.minargs))
# elif inspect.ismethod(object):
elif inspect.isroutine(object):
#java.lang.System.out.println("is a routine")
# method = object
# object = method.im_class
#
# # Java allows overloading so we may have more than one method
# methodArray = object.getMethods()
#
# for eachMethod in methodArray:
# if eachMethod.name == method.__name__:
# paramList = []
# for eachParam in eachMethod.parameterTypes:
# paramList.append(eachParam.__name__)
#
# paramString = string.join(paramList, ', ')
#
# # create a Python style string a la PyCrust
# # we're showing the parameter type rather than the
# # parameter name, since that's all we can get
# # we need to show multiple methods for overloading
# # TODO improve message format
# # do we want to show the method visibility?
# # how about exceptions?
# # note: name, return type and exceptions same for
# # EVERY overloaded method
#
#
# tip = "%s(%s) -> %s" % (eachMethod.name, paramString,
# eachMethod.returnType)
# tipList.append(tip)
if hasattr(object, "argslist"):
for args in object.argslist:
if args is not None:
# for now
tipList.append(str(args.method))
# elif callable(object):
# argspec = str(object.__call__)
# # these don't seem to be very accurate
# if hasattr(object.__call__, "maxargs"):
# tipList.append("maxargs?: " + str(object.__call__.maxargs))
# if hasattr(object.__call__, "minargs"):
# tipList.append("minargs?: " + str(object.__call__.minargs))
# elif inspect.isfunction(object):
# print "is function"
if (len(tipList) == 0):
if hasattr(object, "__name__") and \
hasattr(__builtin__, object.__name__):
# try to get arguments for any other "old-style" builtin
# functions (see __builtin__.java, classDictInit() method)
methods = \
java.lang.Class.getMethods(org.python.core.__builtin__)
for method in methods:
if method.name == object.__name__:
tipList.append(str(method))
argspec = "a built-in Python function"
else:
# last-ditch: try possible __call__ methods of new-style
# objects
for possible_call_method in \
ghidra.python.PythonCodeCompletionFactory.getCallMethods(object):
signature = str(possible_call_method)
# clean up the method signature a bit, so it looks sane
signature = \
signature.replace("$1exposed_", ".").replace(".__call__", "")
tipList.append(signature)
calltip = (name, argspec, string.join(tipList, "\n"))
return calltip
def ispython(object):
"""
Figure out if this is Python code or Java code..
"""
pyclass = 0
pycode = 0
pyinstance = 0
if inspect.isclass(object):
try:
object.__doc__
pyclass = 1
except AttributeError:
pyclass = 0
elif inspect.ismethod(object):
try:
# changed for Jython 2.2a1
#object.__dict__
object.__str__
pycode = 1
except AttributeError:
pycode = 0
else: # I guess an instance of an object falls here
try:
# changed for Jython 2.2a1
#object.__dict__
object.__str__
pyinstance = 1
except AttributeError:
pyinstance = 0
return pyclass | pycode | pyinstance

View File

@@ -0,0 +1,48 @@
## ###
# 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.
##
"""
User-supplied customizations go here.
"""
# nice-to-have: place 'java' and 'ghidra' into the local namespace, so you
# can do fun things like "dir(java.lang)"
import java
import ghidra
import __main__
__main__.java = java
__main__.ghidra = ghidra
# fix Jython "bug": unknown type 'javainstance' or 'javapackage' even though
# that is the type Jython gives us if we ask type(<someObject>) or
# type(ghidra) (respectively)
import __builtin__
import org.python.core
# changed by Jim 20090528 for Jython 2.5
# not sure why I even put these here... the first one might be troublesome
#__builtin__.javainstance = org.python.core.PyJavaInstance
#__builtin__.javapackage = org.python.core.PyJavaPackage
#__builtin__.javaclass = org.python.core.PyJavaClass
__builtin__.javainstance = org.python.core.PyObjectDerived
__builtin__.javapackage = org.python.core.PyJavaPackage
__builtin__.javaclass = org.python.core.PyJavaType
# changed by Jim 20090528 for Jython 2.5
# REMOVED collections stuff
# OOPS still need this
import sys
# Ghidra documentation
import ghidradoc

View File

@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<tocroot>
<tocref id="Ghidra Functionality">
<tocref id="Scripting">
<tocdef id="Jython Interpreter" sortgroup="z" text="Jython Interpreter" target="help/topics/Jython/interpreter.html" />
</tocref>
</tocref>
</tocroot>

View File

@@ -0,0 +1,171 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML>
<HEAD>
<TITLE>Jython Interpreter</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="Jython"></A>Jython Interpreter</H1>
<P>
The Ghidra <I>Jython Interpreter</I> provides a full general-purpose Jython 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>Jython 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
Jython <TT><FONT COLOR="RED">NameError</FONT></TT>.
</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>
<H2><A name="Interrupt_Interpreter"></A>Interrupt <IMG border="0" src="images/dialog-cancel.png"></H2>
<BLOCKQUOTE>
<P>
This command issues a keyboard interrupt to the interpreter, which can be used to interrupt
long running commands or loops.
</P>
</BLOCKQUOTE>
<H2><A name="Reset_Interpreter"></A>Reset <IMG border="0" src="images/reload3.png"></H2>
<BLOCKQUOTE>
<P>
This command resets the interpreter, which clears the display and resets all state.
</P>
</BLOCKQUOTE>
<H2>Keybindings</H2>
<BLOCKQUOTE>
<P>
The Ghidra <I>Jython Interpreter</I> supports the following hard-coded keybindings:
<UL>
<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>Jython 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> Jython function has been altered by the Ghidra <I>Jython 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>
Jython function on it. Failure to do so will result in a Jython <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>
<P align="left" class="providedbyplugin">Provided by: <I>JythonPlugin</I></P>
<P>&nbsp;</P>
<BR>
<BR>
<BR>
</BODY>
</HTML>

View File

@@ -0,0 +1,559 @@
/* ###
* 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.jython;
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 GhidraJythonInterpreter 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 GhidraJythonInterpreter get() {
// Initialize the python environment if necessary. Only needs to happen once.
if (!pythonInitialized) {
try {
// Setup python home directory
JythonUtils.setupJythonHomeDir();
// Setup python cache directory
JythonUtils.setupJythonCacheDir(TaskMonitor.DUMMY);
// Indicate that we've initialized the python environment, which should
// only happen once.
pythonInitialized = true;
}
catch (Exception e) {
Msg.showError(GhidraJythonInterpreter.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 GhidraJythonInterpreter(state);
}
/**
* Creates a new Ghidra python interpreter object.
*
* @param state The initial system state of the interpreter.
*/
private GhidraJythonInterpreter(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.newStringOrUnicode(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__");
}
/**
* 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.getEnabledScriptSourceDirectories()) {
systemState.path.append(Py.newStringOrUnicode(resourceFile.getFile(false).getAbsolutePath()));
}
for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedCompiledSourceBundlePaths()) {
systemState.path.append(Py.newStringOrUnicode(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.newStringOrUnicode(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, JythonScript script)
throws PyException, IllegalStateException {
if (cleanedUp) {
throw new IllegalStateException(
"Ghidra python interpreter has already been cleaned up.");
}
initializePythonPath();
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, JythonScript script)
throws IllegalStateException {
if (cleanedUp) {
throw new IllegalStateException(
"Ghidra python interpreter has already been cleaned up.");
}
initializePythonPath();
injectScriptHierarchy(script);
Py.getThreadState().tracefunc = interruptTraceFunction;
// The Python import system sets the __file__ attribute to the file it's executing
setVariable("__file__", new PyString(file.getAbsolutePath()));
// 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");
StringBuilder dbgCmds = new StringBuilder();
dbgCmds.append("import pydevd;");
dbgCmds.append("pydevd.threadingCurrentThread().__pydevd_main_thread = True;");
dbgCmds.append("pydevd.settrace(host=\"" + localhost.getHostName() +
"\", port=" + PyDevUtils.PYDEV_REMOTE_DEBUGGER_PORT + ", suspend=False);");
exec(dbgCmds.toString());
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) {
try {
getSystemState().stderr.invoke("write", new PyString(str + "\n"));
getSystemState().stderr.invoke("flush");
}
catch (PyException e) {
// if the python interp state's stdin/stdout/stderr is messed up, it can throw an error
Msg.error(this, "Failed to write to stderr", e);
}
}
/**
* 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.
*/
void injectScriptHierarchy(JythonScript script) {
if (script == null) {
return;
}
// Inject 'this'
setVariable("this", script);
// 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.
//
// NOTE: We currently do not have a way to safely add protected methods. Disabling
// python.security.respectJavaAccessibility and adding in protected methods in the below
// loop caused an InaccessibleObjectException for some users (relating to core Java
// modules, not the GhidraScript class hierarchy).
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.
* @param caretPos The position of the caret in the input string 'cmd'
* @return A list of possible command completions. Could be empty if there aren't any.
* @see JythonPlugin#getCompletions
*/
List<CodeCompletion> getCommandCompletions(String cmd, boolean includeBuiltins, int caretPos) {
// At this point the caret is assumed to be positioned right after the value we need to
// complete (example: "[complete.Me<caret>, rest, code]"). To make the completion work
// in our case, it's sufficient (albeit naive) to just remove the text on the right side
// of our caret. The later code (on the python's side) will parse the rest properly
// and will generate the completions.
cmd = cmd.substring(0, caretPos);
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-&gt;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,301 @@
/* ###
* 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.jython;
import java.awt.Color;
import java.lang.reflect.Method;
import java.util.*;
import javax.swing.JComponent;
import org.python.core.PyInstance;
import org.python.core.PyObject;
import docking.widgets.label.GDLabel;
import generic.theme.GColor;
import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.framework.options.Options;
import ghidra.util.Msg;
/**
* Generates CodeCompletions from Jython objects.
*
*
*
*/
public class JythonCodeCompletionFactory {
private static List<Class<?>> classes = new ArrayList<>();
private static Map<Class<?>, Color> classToColorMap = new HashMap<>();
/* 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<>();
private static Map<Class<?>, String> classDescription = new HashMap<>();
public static final String COMPLETION_LABEL = "Code Completion Colors";
/* package-level accessibility so that JythonPlugin 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;
//@formatter:off
public static final Color NULL_COLOR = new GColor("color.fg.plugin.jython.syntax.null");
public static final Color FUNCTION_COLOR = new GColor("color.fg.plugin.jython.syntax.function");
public static final Color PACKAGE_COLOR = new GColor("color.fg.plugin.jython.syntax.package");
public static final Color CLASS_COLOR = new GColor("color.fg.plugin.jython.syntax.class");
public static final Color METHOD_COLOR = new GColor("color.fg.plugin.jython.syntax.method");
/* anonymous code chunks */
public static final Color CODE_COLOR = new GColor("color.fg.plugin.jython.syntax.code");
public static final Color INSTANCE_COLOR = new GColor("color.fg.plugin.jython.syntax.instance");
public static final Color SEQUENCE_COLOR = new GColor("color.fg.plugin.jython.syntax.sequence");
public static final Color MAP_COLOR = new GColor("color.fg.plugin.jython.syntax.map");
public static final Color NUMBER_COLOR = new GColor("color.fg.plugin.jython.syntax.number");
/* for weird Jython-specific stuff */
public static final Color SPECIAL_COLOR = new GColor("color.fg.plugin.jython.syntax.special");
//@formatter:on
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(JythonCodeCompletionFactory.class, "Unable to find class: " + className,
cnfe);
}
}
/**
* Creates a new CodeCompletion from the given Jython objects.
*
* @param description description of the new CodeCompletion
* @param insertion what will be inserted to make the code complete
* @param pyObj a Jython Object
* @return A new CodeCompletion from the given Jython objects.
* @deprecated use {@link #newCodeCompletion(String, String, PyObject, String)} instead,
* it allows creation of substituting code completions
*/
@Deprecated
public static CodeCompletion newCodeCompletion(String description, String insertion,
PyObject pyObj) {
return newCodeCompletion(description, insertion, pyObj, "");
}
/**
* Creates a new CodeCompletion from the given Jython objects.
*
* @param description description of the new CodeCompletion
* @param insertion what will be inserted to make the code complete
* @param pyObj a Jython Object
* @param userInput a word we want to complete, can be an empty string.
* It's used to determine which part (if any) of the input should be
* removed before the insertion of the completion
* @return A new CodeCompletion from the given Jython objects.
*/
public static CodeCompletion newCodeCompletion(String description, String insertion,
PyObject pyObj, String userInput) {
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 GDLabel(description);
Iterator<Class<?>> iter = classes.iterator();
while (iter.hasNext()) {
Class<?> testClass = iter.next();
if (testClass.isInstance(pyObj)) {
comp.setForeground(classToColorMap.get(testClass));
break;
}
}
}
int charsToRemove = userInput.length();
return new CodeCompletion(description, insertion, comp, charsToRemove);
}
/**
* Sets up Jython code completion Options.
* @param plugin jython plugin as options owner
* @param options an Options handle
*/
public static void setupOptions(JythonPlugin plugin, Options options) {
includeTypes = options.getBoolean(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT);
options.registerOption(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT, null,
INCLUDE_TYPES_DESCRIPTION);
}
/**
* 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) {
if (name.equals(INCLUDE_TYPES_LABEL)) {
includeTypes = ((Boolean) newValue).booleanValue();
}
else {
Msg.error(JythonCodeCompletionFactory.class, "unknown option '" + name + "'");
}
}
/**
* Returns the Java __call__ methods declared for a Jython object.
*
* Some Jython "methods" in the new-style Jython 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 Jython object
*/
public static Object[] getCallMethods(PyObject obj) {
List<Method> callMethodList = new ArrayList<>();
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,388 @@
/* ###
* 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.jython;
import java.awt.event.KeyEvent;
import java.io.*;
import java.util.List;
import javax.swing.Icon;
import org.python.core.PySystemState;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.*;
import generic.jar.ResourceFile;
import generic.theme.GIcon;
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.HelpLocation;
import ghidra.util.task.*;
import resources.Icons;
/**
* This plugin provides the interactive Jython interpreter.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.COMMON,
shortDescription = "Jython Interpreter",
description = "Provides an interactive Jython Interpreter that is tightly integrated with a loaded Ghidra program.",
servicesRequired = { InterpreterPanelService.class },
isSlowInstallation = true
)
//@formatter:on
public class JythonPlugin extends ProgramPlugin
implements InterpreterConnection, OptionsChangeListener {
private InterpreterConsole console;
private GhidraJythonInterpreter interpreter;
private JythonScript interactiveScript;
private TaskMonitor interactiveTaskMonitor;
private JythonPluginInputThread 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 Jython's built-in functions and properties in the pop-up code completion window.";
private final static boolean INCLUDE_BUILTINS_DEFAULT = true;
private static final Icon ICON = new GIcon("icon.plugin.jython");
private boolean includeBuiltins = INCLUDE_BUILTINS_DEFAULT;
/**
* Creates a new {@link JythonPlugin} object.
*
* @param tool The tool associated with this plugin.
*/
public JythonPlugin(PluginTool tool) {
super(tool);
}
/**
* Gets the plugin's interpreter console.
*
* @return The plugin's interpreter console.
*/
InterpreterConsole getConsole() {
return console;
}
/**
* Gets the plugin's Jython interpreter.
*
* @return The plugin's Jython interpreter. May be null.
*/
GhidraJythonInterpreter getInterpreter() {
return interpreter;
}
/**
* Gets the plugin's interactive script
*
* @return The plugin's interactive script.
*/
JythonScript 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());
createActions();
}
/**
* Creates various actions for the plugin.
*/
private void createActions() {
// Interrupt Interpreter
DockingAction interruptAction = new DockingAction("Interrupt Interpreter", getName()) {
@Override
public void actionPerformed(ActionContext context) {
interrupt();
}
};
interruptAction.setDescription("Interrupt Interpreter");
interruptAction.setToolBarData(
new ToolBarData(Icons.NOT_ALLOWED_ICON, null));
interruptAction.setEnabled(true);
interruptAction.setKeyBindingData(
new KeyBindingData(KeyEvent.VK_I, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
interruptAction.setHelpLocation(new HelpLocation(getTitle(), "Interrupt_Interpreter"));
console.addAction(interruptAction);
// Reset Interpreter
DockingAction resetAction = new DockingAction("Reset Interpreter", getName()) {
@Override
public void actionPerformed(ActionContext context) {
reset();
}
};
resetAction.setDescription("Reset Interpreter");
resetAction.setToolBarData(
new ToolBarData(Icons.REFRESH_ICON, null));
resetAction.setEnabled(true);
resetAction.setKeyBindingData(
new KeyBindingData(KeyEvent.VK_D, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
resetAction.setHelpLocation(new HelpLocation(getTitle(), "Reset_Interpreter"));
console.addAction(resetAction);
}
/**
* 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 Jython 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 Jython...", () -> {
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("Jython");
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 = GhidraJythonInterpreter.get();
// Setup code completion. This currently has to be done after the interpreter
// is created. Otherwise an exception will occur.
JythonCodeCompletionFactory.setupOptions(this, options);
}
else {
inputThread.shutdown();
inputThread = null;
interpreter.cleanup();
interpreter = GhidraJythonInterpreter.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 JythonScript describing the state of the interactive prompt.
// This allows things like currentProgram and currentAddress to dynamically reflect
// what's happening in the listing. Injecting the script hierarchy early here allows
// code completion to work before commands are entered.
interactiveScript = new JythonScript();
interactiveScript.set(
new GhidraState(tool, tool.getProject(), getCurrentProgram(), getProgramLocation(),
getProgramSelection(), getProgramHighlight()),
interactiveTaskMonitor, new PrintWriter(getConsole().getStdOut()));
interpreter.injectScriptHierarchy(interactiveScript);
interactiveTaskMonitor = new JythonInteractiveTaskMonitor(console.getStdOut());
// Start the input thread that receives jython commands to execute.
inputThread = new JythonPluginInputThread(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(JythonCodeCompletionFactory.COMPLETION_LABEL)) {
JythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
}
else if (optionName.equals(JythonCodeCompletionFactory.INCLUDE_TYPES_LABEL)) {
JythonCodeCompletionFactory.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) {
return getCompletions(cmd, cmd.length());
}
/**
* Returns a list of possible command completion values at the given position.
*
* @param cmd current command line (without prompt)
* @param caretPos The position of the caret in the input string 'cmd'
* @return A list of possible command completion values. Could be empty if there aren't any.
*/
@Override
public List<CodeCompletion> getCompletions(String cmd, int caretPos) {
// Refresh the environment
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
interactiveScript.set(
new GhidraState(tool, tool.getProject(), currentProgram, currentLocation,
currentSelection, currentHighlight),
interactiveTaskMonitor, console.getOutWriter());
return interpreter.getCommandCompletions(cmd, includeBuiltins, caretPos);
}
@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.shutdown();
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();
}
/**
* Interrupts what the interpreter is currently doing.
*/
public void interrupt() {
if (interpreter == null) {
return;
}
interpreter.interrupt(inputThread.getJythonPluginExecutionThread());
console.setPrompt(interpreter.getPrimaryPrompt());
}
/**
* Resets the interpreter's state.
*/
public void reset() {
// Do an interrupt in case there is a loop or something running
interrupt();
resetInterpreter();
}
@Override
public String getTitle() {
return "Jython";
}
@Override
public String toString() {
return getPluginDescription().getName();
}
@Override
public Icon getIcon() {
return ICON;
}
/**
* Prints a welcome message to the console.
*/
private void welcome() {
console.getOutWriter().println("Jython 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 JythonInteractiveTaskMonitor extends TaskMonitorAdapter {
private PrintWriter output = null;
public JythonInteractiveTaskMonitor(PrintWriter stdOut) {
output = stdOut;
}
public JythonInteractiveTaskMonitor(OutputStream stdout) {
this(new PrintWriter(stdout));
}
@Override
public void setMessage(String message) {
output.println("<jython-interactive>: " + message);
}
}
}

View File

@@ -0,0 +1,97 @@
/* ###
* 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.jython;
import java.io.File;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
import org.python.core.PyException;
import db.Transaction;
import generic.jar.ResourceFile;
import ghidra.app.script.GhidraState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
/**
* Thread responsible for executing a jython command for the plugin.
*/
class JythonPluginExecutionThread extends Thread {
private JythonPlugin plugin;
private String cmd;
private AtomicBoolean moreInputWanted;
/**
* Creates a new jython plugin execution thread that executes the given command for the given
* plugin.
*
* @param plugin The jython plugin to execute the command for.
* @param cmd The jython command to execute.
* @param moreInputWanted Gets set to indicate that the executed command expects more input.
*/
JythonPluginExecutionThread(JythonPlugin plugin, String cmd, AtomicBoolean moreInputWanted) {
super("Jython plugin execution thread");
this.plugin = plugin;
this.cmd = cmd;
this.moreInputWanted = moreInputWanted;
}
@Override
public void run() {
TaskMonitor interactiveTaskMonitor = plugin.getInteractiveTaskMonitor();
JythonScript interactiveScript = plugin.getInteractiveScript();
Program program = plugin.getCurrentProgram();
// Setup transaction for the execution.
try (Transaction tx = program != null ? program.openTransaction("Jython command") : null) {
// Setup Ghidra state to be passed into interpreter
interactiveTaskMonitor.clearCancelled();
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
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
moreInputWanted.set(false);
moreInputWanted.set(plugin.getInterpreter().push(cmd, plugin.getInteractiveScript()));
}
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 {
interactiveScript.end(false); // end any transactions the script may have started
}
}
}

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.jython;
import java.io.*;
import java.util.concurrent.atomic.AtomicBoolean;
import ghidra.util.Msg;
/**
* Thread responsible for getting interactive lines of jython from the plugin.
* This class also kicks off the execution of that line in a new {@link JythonPluginExecutionThread}.
*/
class JythonPluginInputThread extends Thread {
private static int generationCount = 0;
private final JythonPlugin plugin;
private final AtomicBoolean moreInputWanted = new AtomicBoolean(false);
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
private final InputStream consoleStdin;
private JythonPluginExecutionThread jythonExecutionThread;
/**
* Creates a new jython input thread that gets a line of jython input from the given plugin.
*
* @param plugin The jython plugin to get input from.
*/
JythonPluginInputThread(JythonPlugin plugin) {
super("Jython plugin input thread (generation " + ++generationCount + ")");
this.plugin = plugin;
this.consoleStdin = plugin.getConsole().getStdin();
}
/**
* Gets the last jython plugin execution thread that ran.
*
* @return The last jython plugin execution thread that ran. Could be null if one never ran.
*/
JythonPluginExecutionThread getJythonPluginExecutionThread() {
return jythonExecutionThread;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(consoleStdin))) {
String line;
while (!shutdownRequested.get() && (line = reader.readLine()) != null) {
// Execute the line in a new thread
jythonExecutionThread =
new JythonPluginExecutionThread(plugin, line, moreInputWanted);
jythonExecutionThread.start();
try {
// Wait for the execution to finish
jythonExecutionThread.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(JythonPluginInputThread.class,
"Internal error reading commands from interpreter console. Please reset the interpreter.",
e);
}
}
/**
* Causes the the background thread's run() loop to exit.
* <p>
* Causes background thread's exit by closing the inputstream it is looping on.
*/
void shutdown() {
try {
shutdownRequested.set(true);
consoleStdin.close();
}
catch (IOException e) {
// shouldn't happen, ignore
}
}
}

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.jython;
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 JythonRun implements GhidraLaunchable {
@Override
public void launch(GhidraApplicationLayout layout, String[] args) {
// Initialize the application
ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration();
Application.initializeApplication(layout, configuration);
// Setup jython home directory
try {
JythonUtils.setupJythonHomeDir();
}
catch (IOException e) {
Msg.showError(JythonRun.class, null, "Jython home directory", e.getMessage());
System.exit(1);
}
// Setup jython cache directory
try {
JythonUtils.setupJythonCacheDir(configuration.getTaskMonitor());
}
catch (IOException e) {
Msg.showError(JythonRun.class, null, "Jython cache directory", e.getMessage());
System.exit(1);
}
catch (CancelledException e) {
Msg.showError(JythonRun.class, null, "Operation cancelled", e.getMessage());
System.exit(1);
}
// Pass control to Jython
jython.main(args);
}
}

View File

@@ -0,0 +1,180 @@
/* ###
* 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.jython;
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 Jython version of a {@link GhidraScript}.
*/
public class JythonScript extends GhidraScript {
static final String JYTHON_INTERPRETER = "ghidra.jython.interpreter";
private AtomicBoolean interpreterRunning = new AtomicBoolean();
@Override
public void run() {
// Try to get the interpreter from an existing script state.
GhidraJythonInterpreter interpreter =
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
// Are we being called from an already running JythonScript with existing state?
if (interpreter != null) {
runInExistingEnvironment(interpreter);
}
else {
runInNewEnvironment();
}
}
@Override
public void runScript(String scriptName, GhidraState scriptState) throws Exception {
GhidraJythonInterpreter interpreter =
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
if (interpreter == null) {
interpreter = GhidraJythonInterpreter.get();
if (interpreter == null) {
throw new AssertException("Could not get Ghidra Jython interpreter!");
}
}
ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName);
if (scriptSource != null) {
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 JythonScript) {
ghidraScript.set(scriptState, monitor, writer);
JythonScript jythonScript = (JythonScript) ghidraScript;
interpreter.execFile(jythonScript.getSourceFile(), jythonScript);
}
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(GhidraJythonInterpreter 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 GhidraJythonInterpreter interpreter = GhidraJythonInterpreter.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(JYTHON_INTERPRETER, interpreter);
// Execute the script in a new thread.
JythonScriptExecutionThread executionThread =
new JythonScriptExecutionThread(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(JYTHON_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 "Jython";
}
}

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.jython;
import java.util.concurrent.atomic.AtomicBoolean;
import org.python.core.PyException;
/**
* Thread responsible for executing a jython script from a file.
*/
class JythonScriptExecutionThread extends Thread {
private JythonScript script;
private GhidraJythonInterpreter interpreter;
private AtomicBoolean interpreterRunning;
/**
* Creates a new jython script execution thread that executes the given jython script.
*
* @param script The jython script to execute.
* @param interpreter The jython interpreter to use for execution.
* @param interpreterRunning Gets set to indicate whether or not the interpreter is still running the script.
*/
JythonScriptExecutionThread(JythonScript script, GhidraJythonInterpreter interpreter,
AtomicBoolean interpreterRunning) {
super("Jython 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,108 @@
/* ###
* 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.jython;
import java.io.*;
import java.util.regex.Pattern;
import generic.jar.ResourceFile;
import ghidra.app.script.*;
public class JythonScriptProvider extends GhidraScriptProvider {
private static final Pattern BLOCK_COMMENT = Pattern.compile("'''");
@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();
}
/**
* {@inheritDoc}
*
* <p>
* In Jython this is a triple single quote sequence, "'''".
*
* @return the Pattern for Jython block comment openings
*/
@Override
public Pattern getBlockCommentStart() {
return BLOCK_COMMENT;
}
/**
* {@inheritDoc}
*
* <p>
* In Jython this is a triple single quote sequence, "'''".
*
* @return the Pattern for Jython block comment openings
*/
@Override
public Pattern getBlockCommentEnd() {
return BLOCK_COMMENT;
}
@Override
public String getCommentCharacter() {
return "#";
}
@Override
protected String getCertifyHeaderStart() {
return "## ###";
}
@Override
protected String getCertificationBodyPrefix() {
return "#";
}
@Override
protected String getCertifyHeaderEnd() {
return "##";
}
@Override
public String getDescription() {
return "Jython";
}
@Override
public String getExtension() {
return ".py";
}
@Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws GhidraScriptLoadException {
try {
Class<?> clazz = Class.forName(JythonScript.class.getName());
GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance();
script.setSourceFile(sourceFile);
return script;
}
catch (Exception e) {
throw new GhidraScriptLoadException(e);
}
}
}

View File

@@ -0,0 +1,108 @@
/* ###
* 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.jython;
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 JythonUtils {
public static final String JYTHON_NAME = "jython-2.7.3";
public static final String JYTHON_CACHEDIR = "jython_cachedir";
public static final String JYTHON_SRC = "jython-src";
/**
* Sets up the jython home directory. This is the directory that has the "Lib" directory in it.
*
* @return The jython home directory.
* @throws IOException If there was a disk-related problem setting up the home directory.
*/
public static File setupJythonHomeDir() throws IOException {
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
File jythonHomeDir =
Application.getModuleDataSubDirectory(jythonModuleDir.getName(), JYTHON_NAME)
.getFile(false);
if (!jythonHomeDir.exists()) {
throw new IOException("Failed to find the jython home directory at: " + jythonHomeDir);
}
System.setProperty("jython.home", jythonHomeDir.getAbsolutePath());
return jythonHomeDir;
}
/**
* Sets up the jython cache directory. This is a temporary space that jython 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 jython 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 setupJythonCacheDir(TaskMonitor monitor)
throws CancelledException, IOException {
File devDir = new File(Application.getUserSettingsDirectory(), "dev");
File cacheDir = new File(devDir, JYTHON_CACHEDIR);
if (!FileUtilities.mkdirs(cacheDir)) {
throw new IOException("Failed to create the jython cache directory at: " + cacheDir);
}
File jythonSrcDestDir = new File(cacheDir, JYTHON_SRC);
if (!FileUtilities.createDir(jythonSrcDestDir)) {
throw new IOException(
"Failed to create the " + JYTHON_SRC + " directory at: " + jythonSrcDestDir);
}
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
File jythonSrcDir = new File(jythonModuleDir, JYTHON_SRC);
if (!jythonSrcDir.exists()) {
try {
jythonSrcDir = Application.getModuleDataSubDirectory(jythonModuleDir.getName(),
JYTHON_SRC).getFile(false);
}
catch (FileNotFoundException e) {
throw new IOException("Failed to find the module's " + JYTHON_SRC + " directory");
}
}
try {
FileUtilities.copyDir(jythonSrcDir, jythonSrcDestDir, f -> f.getName().endsWith(".py"),
monitor);
}
catch (IOException e) {
throw new IOException(
"Failed to copy " + JYTHON_SRC + " files to: " + jythonSrcDestDir);
}
System.setProperty("python.cachedir.skip", "false");
System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
System.setProperty("python.path", jythonSrcDestDir.getAbsolutePath());
return cacheDir;
}
}

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.jython;
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() {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,173 @@
/* ###
* 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.jython;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
/**
* Tests for the Ghidra Python Interpreter's code completion functionality.
*/
public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTest {
private String simpleTestProgram = """
my_int = 32
my_bool = True
my_string = 'this is a string'
my_list = ["a", 2, 5.3, my_string]
my_tuple = (1, 2, 3)
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
mY_None = None
i = 5
def factorial(n):
return 1 if n == 0 else n * factorial(n-1)
def error_function():
raise IOError("An IO error occurred!")
class Employee:
def __init__(self, id, name):
self.id = id
self.name = name
def getId(self):
return self.id
def getName(self):
return self.name
employee = Employee(42, "Bob")
""".stripIndent();
@Rule
public TemporaryFolder tempScriptFolder = new TemporaryFolder();
private GhidraJythonInterpreter interpreter;
@Before
public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null);
interpreter = GhidraJythonInterpreter.get();
executeJythonProgram(simpleTestProgram);
}
@After
public void tearDown() throws Exception {
interpreter.cleanup();
GhidraScriptUtil.dispose();
}
@Test
public void testBasicCodeCompletion() {
// test the "insertion" field
// it should be equal to the full name of a variable we want to complete
List<String> completions = List.of("my_bool", "my_dictionary", "my_int", "my_list",
"mY_None", "my_string", "my_tuple");
assertCompletionsInclude("My", completions);
assertCompletionsInclude("employee.Get", List.of("getId", "getName"));
assertCompletionsInclude("('noise', (1 + fact", List.of("factorial"));
}
@Test
public void testCharsToRemoveField() {
// 'charsToRemove' field should be equal to the length of
// a part of variable/function/method name we are trying to complete here.
// This allows us to correctly put a completion in cases when we really
// just want to replace a piece of text (i.e. "CURRENTAddress" => "currentAddress")
// rather than simply 'complete' it.
assertCharsToRemoveEqualsTo("my_int", "my_int".length());
assertCharsToRemoveEqualsTo("employee.get", "get".length());
assertCharsToRemoveEqualsTo("('noise', (1 + fact", "fact".length());
assertCharsToRemoveEqualsTo("employee.", 0);
assertCharsToRemoveEqualsTo("employee.getId(", 0);
}
@Test
public void testCompletionsFromArbitraryCaretPositions() {
// '<caret>' designates the position of the caret
String testCase;
testCase = "MY_TUP<caret>";
assertCompletionsInclude(testCase, List.of("my_tuple"));
assertCharsToRemoveEqualsTo(testCase, "my_tup".length());
testCase = "employee.Get<caret>; (4, 2+2)";
assertCompletionsInclude(testCase, List.of("getId", "getName"));
assertCharsToRemoveEqualsTo(testCase, "Get".length());
testCase = "bar = e<caret> if 2+2==4 else 'baz'";
assertCompletionsInclude(testCase, List.of("error_function", "employee"));
assertCharsToRemoveEqualsTo(testCase, "e".length());
}
private void assertCompletionsInclude(String command, Collection<String> expectedCompletions) {
Set<String> completions = getCodeCompletions(command)
.stream()
.map(c -> c.getInsertion())
.collect(Collectors.toSet());
var missing = new HashSet<String>(expectedCompletions);
missing.removeAll(completions);
if (!missing.isEmpty()) {
Assert.fail("Could't find these completions: " + missing);
}
}
private void assertCharsToRemoveEqualsTo(String command, int expectedCharsToRemove) {
for (CodeCompletion comp : getCodeCompletions(command)) {
assertEquals(String.format("%s; field 'charsToRemove' ", comp), expectedCharsToRemove,
comp.getCharsToRemove());
}
}
private List<CodeCompletion> getCodeCompletions(String command) {
// extract the caret position and generate completions relative to that place
int caretPos = command.indexOf("<caret>");
command.replace("<caret>", "");
if (caretPos == -1) {
caretPos = command.length();
}
return interpreter.getCommandCompletions(command, false, caretPos);
}
private void executeJythonProgram(String code) {
try {
File tempFile = tempScriptFolder.newFile();
FileUtils.writeStringToFile(tempFile, code, Charset.defaultCharset());
interpreter.execFile(new ResourceFile(tempFile), null);
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,97 @@
/* ###
* 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.jython;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
/**
* Tests the Ghidra python interpreter's functionality.
*/
public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
private ByteArrayOutputStream out;
private GhidraJythonInterpreter interpreter;
@Before
public void setUp() throws Exception {
out = new ByteArrayOutputStream();
GhidraScriptUtil.initialize(new BundleHost(), null);
interpreter = GhidraJythonInterpreter.get();
interpreter.setOut(out);
interpreter.setErr(out);
}
@After
public void tearDown() throws Exception {
out.reset();
interpreter.cleanup();
GhidraScriptUtil.dispose();
}
/**
* Tests that the interpreter's "push" method is working by executing a simple line of python.
*/
@Test
public void testJythonPush() {
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 testJythonExecFile() {
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 testJythonSiteCustomize() {
interpreter.push("help", null);
assertTrue(out.toString().contains("Press 'F1'"));
}
/**
* Tests that cleaning the interpreter invalidates it.
*/
@Test
public void testJythonCleanupInvalidation() {
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,62 @@
/* ###
* 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.jython;
import static org.junit.Assert.*;
import org.junit.*;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
/**
* Tests the Python Plugin functionality.
*/
public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private JythonPlugin plugin;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null);
tool.addPlugin(JythonPlugin.class.getName());
plugin = env.getPlugin(JythonPlugin.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
env.dispose();
}
/**
* Tests that issuing a reset from the plugin resets the interpreter.
*/
@Test
public void testJythonPluginReset() {
GhidraJythonInterpreter origInterpreter = plugin.getInterpreter();
plugin.reset();
GhidraJythonInterpreter newInterpreter = plugin.getInterpreter();
assertNotSame(origInterpreter, newInterpreter);
}
}

View File

@@ -0,0 +1,237 @@
/* ###
* 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.jython;
import static org.junit.Assert.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.swing.KeyStroke;
import org.junit.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
GhidraScriptUtil.initialize(new BundleHost(), null);
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
if (Files.notExists(userScriptDir)) {
Files.createDirectories(userScriptDir);
}
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
}
@Test
public void testDetailedJythonScript() {
String descLine1 = "This script exists to check that the info on";
String descLine2 = "a script that has extensive documentation is";
String descLine3 = "properly parsed and represented.";
String author = "Fake Name";
String categoryTop = "Test";
String categoryBottom = "ScriptInfo";
String keybinding = "ctrl shift COMMA";
String menupath = "File.Run.Detailed Script";
String importPackage = "detailStuff";
ResourceFile scriptFile = null;
try {
//@formatter:off
scriptFile = createTempPyScriptFileWithLines(
"'''",
"This is a test block comment. It will be ignored.",
"@category NotTheRealCategory",
"'''",
"#" + descLine1,
"#" + descLine2,
"#" + descLine3,
"#@author " + author,
"#@category " + categoryTop + "." + categoryBottom,
"#@keybinding " + keybinding,
"#@menupath " + menupath,
"#@importpackage " + importPackage,
"print('for a blank class, it sure is well documented!')");
//@formatter:on
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
assertEquals(expectedDescription, info.getDescription());
assertEquals(author, info.getAuthor());
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
assertEquals(importPackage, info.getImportPackage());
String[] actualCategory = info.getCategory();
assertEquals(2, actualCategory.length);
assertEquals(categoryTop, actualCategory[0]);
assertEquals(categoryBottom, actualCategory[1]);
}
@Test
public void testJythonScriptWithBlockComment() {
String description = "Script with a block comment at the top.";
String category = "Test";
ResourceFile scriptFile = null;
try {
//@formatter:off
scriptFile = createTempPyScriptFileWithLines(
"'''",
"This is a test block comment. It will be ignored.",
"@category NotTheRealCategory",
"'''",
"#" + description,
"#@category " + category,
"print 'hello!'");
//@formatter:on
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
assertEquals(description + " \n", info.getDescription());
String[] actualCategory = info.getCategory();
assertEquals(1, actualCategory.length);
assertEquals(category, actualCategory[0]);
}
@Test
public void testJythonScriptWithBlockCommentAndCertifyHeader() {
String description = "Script with a block comment at the top.";
String category = "Test";
ResourceFile scriptFile = null;
try {
//@formatter:off
scriptFile = createTempPyScriptFileWithLines(
"## ###",
"# IP: GHIDRA",
"# ",
"# Some license text...",
"# you may not use this file except in compliance with the License.",
"# ",
"# blah blah blah",
"##",
"",
"'''",
"This is a test block comment. It will be ignored.",
"@category NotTheRealCategory",
"'''",
"#" + description,
"#@category " + category,
"print 'hello!'");
//@formatter:on
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
assertEquals(description + " \n", info.getDescription());
String[] actualCategory = info.getCategory();
assertEquals(1, actualCategory.length);
assertEquals(category, actualCategory[0]);
}
@Test
public void testJythonScriptWithoutBlockComment() {
String description = "Script without a block comment at the top.";
String category = "Test";
ResourceFile scriptFile = null;
try {
//@formatter:off
scriptFile = createTempPyScriptFileWithLines(
"#" + description,
"#@category " + category,
"print 'hello!'");
//@formatter:on
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
assertEquals(description + " \n", info.getDescription());
String[] actualCategory = info.getCategory();
assertEquals(1, actualCategory.length);
assertEquals(category, actualCategory[0]);
}
@Test
public void testJythonScriptWithSingleLineBlockComment() {
String description = "Script with a block comment at the top.";
String category = "Test";
ResourceFile scriptFile = null;
try {
//@formatter:off
scriptFile = createTempPyScriptFileWithLines(
"'''This is a test block comment. It will be ignored.'''",
"#" + description,
"#@category " + category,
"print 'hello!'");
//@formatter:on
}
catch (IOException e) {
fail("couldn't create a test script: " + e.getMessage());
}
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
assertEquals(description + " \n", info.getDescription());
String[] actualCategory = info.getCategory();
assertEquals(1, actualCategory.length);
assertEquals(category, actualCategory[0]);
}
private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException {
File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir);
tempFile.deleteOnExit();
ResourceFile tempResourceFile = new ResourceFile(tempFile);
PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream());
for (String line : lines) {
writer.println(line);
}
writer.close();
return tempResourceFile;
}
}

View File

@@ -0,0 +1,145 @@
/* ###
* 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.jython;
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.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
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 JythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private ConsoleService console;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
GhidraScriptUtil.initialize(new BundleHost(), null);
tool.addPlugin(ConsolePlugin.class.getName());
console = tool.getService(ConsoleService.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.dispose();
env.dispose();
}
/**
* Tests that Jython scripts are running correctly.
*
* @throws Exception If an exception occurred while trying to run the script.
*/
@Test
public void testJythonScript() throws Exception {
String script = "ghidra_scripts/python_basics.py";
try {
String output = runPythonScript(Application.getModuleFile("Jython", script));
assertTrue(output.contains("Snoopy"));
}
catch (FileNotFoundException e) {
fail("Could not find jython script: " + script);
}
catch (Exception e) {
fail("Exception occurred trying to run script: " + e.getMessage());
}
}
/**
* Tests that Jython scripts are running correctly.
*
* @throws Exception If an exception occurred while trying to run the script.
*/
@Test
public void testJythonInterpreterGoneFromState() 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("Jython", script), state);
assertTrue(state.getEnvironmentVar(JythonScript.JYTHON_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());
JythonScriptProvider scriptProvider = new JythonScriptProvider();
PrintWriter writer = new PrintWriter(new ByteArrayOutputStream());
JythonScript script = (JythonScript) scriptProvider.getScriptInstance(scriptFile, writer);
script.set(state, TaskMonitor.DUMMY, writer);
script.run();
waitForSwing();
AtomicReference<String> ref = new AtomicReference<>();
runSwing(() -> {
String text = console.getText(0, console.getTextLength());
ref.set(text);
});
String text = ref.get();
return text;
}
}