Merge remote-tracking branch 'origin/Ghidra_12.1'

This commit is contained in:
Ryan Kurtz
2026-04-24 06:21:29 -04:00
49 changed files with 23 additions and 10 deletions
@@ -26,7 +26,7 @@
script-specific code.</LI>
<LI>Of course if you choose Java, the Ghidra script must be written in Java.
An implementation for Python (based on Jython) is also provided.</LI>
An implementation for Python (based on PyGhidra) is also provided.</LI>
</OL>
<P align="center"><IMG border="0" src="images/New_Script_Editor.png" ></P>
@@ -161,8 +161,8 @@
uses the same script file extension. If left unspecified, the first Ghidra script runtime
environment that matches the script's extension will be used.<BR>
<BR>
For example, specify <TT>"@runtime Jython"</TT> if the script is targetted for a Jython 2
runtime environment rather than a Python 3 runtime environment.
For example, specify <TT>"@runtime Jython"</TT> if the script is targeted for a Jython 2
runtime environment rather than a CPython 3 (PyGhidra) runtime environment.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
-4
View File
@@ -1,4 +0,0 @@
DATA SEARCH IGNORE DIR: jython-2.7.4
EXCLUDE FROM GHIDRA JAR: true
MODULE FILE LICENSE: lib/jython-standalone-2.7.4.jar Jython License
-1
View File
@@ -1 +0,0 @@
# Jython
-67
View File
@@ -1,67 +0,0 @@
/* ###
* 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.4"
def JYTHON_DIR = "jython-2.7.4"
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()
}
gradle.taskGraph.whenReady {
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
@@ -1,8 +0,0 @@
##VERSION: 2.0
##MODULE IP: Jython License
##MODULE IP: LGPL 2.1
Module.manifest||GHIDRA||||END|
README.md||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|
@@ -1,21 +0,0 @@
[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]
@@ -1,32 +0,0 @@
## ###
# 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
#@runtime Jython
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!")
@@ -1,113 +0,0 @@
## ###
# 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
#@runtime Jython
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"
@@ -1,91 +0,0 @@
## ###
# 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
#@runtime Jython
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)
@@ -1,25 +0,0 @@
## ###
# 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
#@runtime Jython
runScript("HelloWorldScript.java")
runScript("HelloWorldPopupScript.java")
@@ -1,37 +0,0 @@
## ###
# 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
#@runtime Jython
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)
@@ -1,49 +0,0 @@
## ###
# 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
#@runtime Jython
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))
@@ -1,44 +0,0 @@
## ###
# 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
#@runtime Jython
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")
@@ -1,62 +0,0 @@
## ###
# 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 Import
# @runtime Jython
#
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)
@@ -1,21 +0,0 @@
## ###
# 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
# @runtime Jython
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()
@@ -1,37 +0,0 @@
## ###
# 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
#@runtime Jython
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)
@@ -1,34 +0,0 @@
## ###
# 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
# @runtime Jython
# 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()
@@ -1,22 +0,0 @@
## ###
# 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
# @runtime Jython
# 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
@@ -1,78 +0,0 @@
## ###
# 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
# @runtime Jython
# 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
@@ -1,54 +0,0 @@
## ###
# 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
# @runtime Jython
# 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)
@@ -1,118 +0,0 @@
## ###
# 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
# @runtime Jython
# 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()
@@ -1,194 +0,0 @@
## ###
# 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["access"]:
sig = field["access"] + " " + 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
if method["access"]:
sig = method["access"] + " " + 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()
File diff suppressed because it is too large Load Diff
@@ -1,237 +0,0 @@
## ###
# 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
@@ -1,48 +0,0 @@
## ###
# 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
@@ -1,12 +0,0 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<tocroot>
<tocref id="Ghidra Functionality">
<tocref id="Scripting">
<!-- The sort group places this entry at the bottom of all other entries in the Program
Annotation section. 'z' puts it at the end. The rest makes it unique. -->
<tocdef id="Jython Interpreter" sortgroup="z_Python_Jython" text="Jython Interpreter" target="help/topics/Jython/interpreter.html" />
</tocref>
</tocref>
</tocroot>
@@ -1,171 +0,0 @@
<!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>
File diff suppressed because it is too large Load Diff
@@ -1,301 +0,0 @@
/* ###
* 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();
}
}
@@ -1,391 +0,0 @@
/* ###
* 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.app.script.ScriptControls;
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);
console.addFirstActivationCallback(() -> {
welcome();
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()),
new ScriptControls(console, interactiveTaskMonitor));
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),
new ScriptControls(console, interactiveTaskMonitor));
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, true));
}
@Override
public void setMessage(String message) {
output.println("<jython-interactive>: " + message);
}
}
}
@@ -1,98 +0,0 @@
/* ###
* 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.util.concurrent.atomic.AtomicBoolean;
import org.python.core.PyException;
import db.Transaction;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
import ghidra.app.script.GhidraState;
import ghidra.app.script.ScriptControls;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
/**
* 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();
InterpreterConsole console = plugin.getConsole();
// 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()),
new ScriptControls(console, interactiveTaskMonitor));
// 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 {
console.getErrWriter()
.println(
"Suppressing exception: " + PyException.exceptionClassName(pye.type));
}
}
catch (StackOverflowError soe) {
console.getErrWriter().println("Stack overflow!");
}
finally {
interactiveScript.end(false); // end any transactions the script may have started
}
}
}
@@ -1,105 +0,0 @@
/* ###
* 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 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
}
}
}
@@ -1,65 +0,0 @@
/* ###
* 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);
}
}
@@ -1,187 +0,0 @@
/* ###
* 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, errorWriter);
if (ghidraScript == null) {
throw new IllegalArgumentException("Script does not exist: " + scriptName);
}
if (scriptState == state) {
updateStateFromVariables();
}
try {
interpreter.saveLocals();
interpreter.clearLocals();
if (ghidraScript instanceof JythonScript) {
ghidraScript.set(scriptState);
JythonScript jythonScript = (JythonScript) ghidraScript;
interpreter.execFile(jythonScript.getSourceFile(), jythonScript);
}
else {
ghidraScript.execute(scriptState, getControls());
}
}
finally {
interpreter.restoreLocals();
}
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";
}
}
@@ -1,72 +0,0 @@
/* ###
* 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);
}
}
}
@@ -1,53 +0,0 @@
/* ###
* 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 generic.jar.ResourceFile;
import ghidra.app.script.*;
/**
* A {@link GhidraScriptProvider} used to run Jython scripts
*/
public class JythonScriptProvider extends AbstractPythonScriptProvider {
@Override
public String getDescription() {
return "Jython";
}
@Override
public String getRuntimeEnvironmentName() {
return "Jython";
}
@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);
}
}
}
@@ -1,108 +0,0 @@
/* ###
* 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.4";
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;
}
}
@@ -1,41 +0,0 @@
/* ###
* 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() {
}
}
@@ -1,172 +0,0 @@
/* ###
* 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.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.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get();
executeJythonProgram(simpleTestProgram);
}
@After
public void tearDown() throws Exception {
interpreter.cleanup();
GhidraScriptUtil.releaseBundleHostReference();
}
@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());
}
}
}
@@ -1,96 +0,0 @@
/* ###
* 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.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.acquireBundleHostReference();
interpreter = GhidraJythonInterpreter.get();
interpreter.setOut(out);
interpreter.setErr(out);
}
@After
public void tearDown() throws Exception {
out.reset();
interpreter.cleanup();
GhidraScriptUtil.releaseBundleHostReference();
}
/**
* 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.
}
}
}
@@ -1,61 +0,0 @@
/* ###
* 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.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.acquireBundleHostReference();
tool.addPlugin(JythonPlugin.class.getName());
plugin = env.getPlugin(JythonPlugin.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.releaseBundleHostReference();
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);
}
}
@@ -1,236 +0,0 @@
/* ###
* 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.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setUp() throws Exception {
GhidraScriptUtil.acquireBundleHostReference();
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.releaseBundleHostReference();
}
@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;
}
}
@@ -1,144 +0,0 @@
/* ###
* 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.*;
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.acquireBundleHostReference();
tool.addPlugin(ConsolePlugin.class.getName());
console = tool.getService(ConsoleService.class);
}
@After
public void tearDown() throws Exception {
GhidraScriptUtil.releaseBundleHostReference();
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, new ScriptControls(writer, writer, TaskMonitor.DUMMY));
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;
}
}