mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-4659: Renaming "Python" module to "Jython"
This commit is contained in:
4
Ghidra/Features/Jython/Module.manifest
Normal file
4
Ghidra/Features/Jython/Module.manifest
Normal file
@@ -0,0 +1,4 @@
|
||||
DATA SEARCH IGNORE DIR: jython-2.7.3
|
||||
EXCLUDE FROM GHIDRA JAR: true
|
||||
MODULE FILE LICENSE: lib/jython-standalone-2.7.3.jar Jython License
|
||||
|
||||
65
Ghidra/Features/Jython/build.gradle
Normal file
65
Ghidra/Features/Jython/build.gradle
Normal file
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javadoc.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
eclipse.project.name = 'Features Jython'
|
||||
|
||||
|
||||
def JYTHON = "org.python:jython-standalone:2.7.3"
|
||||
def JYTHON_DIR = "jython-2.7.3"
|
||||
|
||||
configurations {
|
||||
jython
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':Base')
|
||||
jython JYTHON
|
||||
api JYTHON
|
||||
}
|
||||
|
||||
task jythonUnpack(type: Copy) {
|
||||
description "Unpack Jython Lib"
|
||||
|
||||
// Without this, the copyTask will unzip the file to check for "up to date"
|
||||
onlyIf {
|
||||
!file("build/data/${JYTHON_DIR}").exists()
|
||||
}
|
||||
|
||||
from zipTree(configurations.jython.singleFile)
|
||||
include "Lib/**"
|
||||
|
||||
destinationDir file("build/data/${JYTHON_DIR}")
|
||||
}
|
||||
|
||||
task jythonSrcCopy(type: Copy) {
|
||||
description "Copy jython-src directory (for Feature Jython)"
|
||||
|
||||
from(file("jython-src"))
|
||||
|
||||
destinationDir file("build/data/jython-src")
|
||||
}
|
||||
|
||||
// Ensure that Jython is usable in development xx
|
||||
rootProject.prepDev.dependsOn jythonUnpack
|
||||
jar.dependsOn jythonUnpack
|
||||
jar.dependsOn jythonSrcCopy
|
||||
8
Ghidra/Features/Jython/certification.manifest
Normal file
8
Ghidra/Features/Jython/certification.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Jython License
|
||||
##MODULE IP: LGPL 2.1
|
||||
Module.manifest||GHIDRA||||END|
|
||||
data/jython.theme.properties||GHIDRA||||END|
|
||||
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
|
||||
src/main/help/help/topics/Jython/interpreter.html||GHIDRA||||END|
|
||||
src/main/resources/images/python.png||GHIDRA||||END|
|
||||
21
Ghidra/Features/Jython/data/jython.theme.properties
Normal file
21
Ghidra/Features/Jython/data/jython.theme.properties
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
[Defaults]
|
||||
|
||||
color.fg.plugin.jython.syntax.class = color.palette.blue
|
||||
color.fg.plugin.jython.syntax.code = color.palette.darkgreen
|
||||
color.fg.plugin.jython.syntax.function = color.palette.green
|
||||
color.fg.plugin.jython.syntax.instance = color.palette.purple
|
||||
color.fg.plugin.jython.syntax.map = color.palette.steelblue
|
||||
color.fg.plugin.jython.syntax.method = color.palette.teal
|
||||
color.fg.plugin.jython.syntax.null = color.palette.red
|
||||
color.fg.plugin.jython.syntax.number = color.palette.darkgray
|
||||
color.fg.plugin.jython.syntax.package = color.palette.darkred
|
||||
color.fg.plugin.jython.syntax.sequence = color.palette.saddlebrown
|
||||
color.fg.plugin.jython.syntax.special = color.palette.darkgreen
|
||||
|
||||
icon.plugin.jython = python.png
|
||||
|
||||
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Adds a comment to a program.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
|
||||
from ghidra.program.model.address.Address import *
|
||||
from ghidra.program.model.listing.CodeUnit import *
|
||||
from ghidra.program.model.listing.Listing import *
|
||||
|
||||
minAddress = currentProgram.getMinAddress()
|
||||
listing = currentProgram.getListing()
|
||||
codeUnit = listing.getCodeUnitAt(minAddress)
|
||||
codeUnit.setComment(codeUnit.PLATE_COMMENT, "AddCommentToProgramScript - This is an added comment!")
|
||||
112
Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py
Normal file
112
Ghidra/Features/Jython/ghidra_scripts/AskScriptPy.py
Normal file
@@ -0,0 +1,112 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example of asking for user input.
|
||||
|
||||
# Note the ability to pre-populate values for some of these variables when AskScript.properties file exists.
|
||||
# Also notice how the previous input is saved.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from ghidra.framework.model import DomainFile
|
||||
from ghidra.framework.model import DomainFolder
|
||||
from ghidra.program.model.address import Address
|
||||
from ghidra.program.model.lang import LanguageCompilerSpecPair
|
||||
from ghidra.program.model.listing import Program
|
||||
from ghidra.util import Msg
|
||||
|
||||
from java.lang import IllegalArgumentException
|
||||
from ghidra.util.exception import CancelledException
|
||||
|
||||
# The presence of the AskScript.properties file in the same location (as AskScript.java)
|
||||
# allows for the following behavior:
|
||||
# - GUI: if applicable, auto-populates the input field with the value in the
|
||||
# .properties file (the first time that input field appears)
|
||||
# - Headless: uses the value in the .properties file for the variable assigned to the
|
||||
# corresponding askXxx() method in the GhidraScript.
|
||||
try:
|
||||
file1 = askFile("FILE", "Choose file:")
|
||||
print "file was: " + str(file1)
|
||||
|
||||
directory1 = askDirectory("Directory", "Choose directory:")
|
||||
print "directory was: " + str(directory1)
|
||||
|
||||
lang = askLanguage("Language Picker", "I want this one!")
|
||||
print "language was: " + lang.toString()
|
||||
|
||||
domFolder = askProjectFolder("Please pick a domain folder!")
|
||||
print "domFolder was: " + domFolder.getName()
|
||||
|
||||
int1 = askInt("integer 1", "enter integer 1")
|
||||
int2 = askInt("integer 2", "enter integer 2")
|
||||
print "int1 + int2 = " + str(int1 + int2)
|
||||
|
||||
long1 = askLong("long 1", "enter long 1")
|
||||
long2 = askLong("long 2", "enter long 2")
|
||||
print "long1 + long2 = " + str(long1 + long2)
|
||||
|
||||
address1 = askAddress("address 1", "enter address 1")
|
||||
address2 = askAddress("address 2", "enter address 2")
|
||||
print "address1 + address2 = " + address1.add(address2.getOffset()).toString()
|
||||
|
||||
#bytes = askBytes("bytes", "enter byte pattern")
|
||||
#for b in bytes:
|
||||
# print "b = " + str(b & 0xff)
|
||||
|
||||
try:
|
||||
prog = askProgram("Please choose a program to open.")
|
||||
print "Program picked: " + prog.getName()
|
||||
finally:
|
||||
if prog is not None:
|
||||
prog.release(this)
|
||||
|
||||
domFile = askDomainFile("Which domain file would you like?")
|
||||
print "Domain file: " + domFile.getName()
|
||||
|
||||
d1 = askDouble("double 1", "enter double 1")
|
||||
d2 = askDouble("double 2", "enter double 2")
|
||||
print "d1 + d2 = " + str(d1 + d2)
|
||||
|
||||
myStr = askString("String Specification", "Please type a string: ")
|
||||
myOtherStr = askString("Another String Specification", "Please type another string: ", "replace me!")
|
||||
print "You typed: " + myStr + " and " + myOtherStr
|
||||
|
||||
choice = askChoice("Choice", "Please choose one", [ "grumpy", "dopey", "sleepy", "doc", "bashful" ], "bashful")
|
||||
print "Choice? " + choice
|
||||
|
||||
choices1 = askChoices("Choices 1", "Please choose one or more numbers.", [ 1, 2, 3, 4, 5, 6 ])
|
||||
print "Choices 1: "
|
||||
for intChoice in choices1:
|
||||
print str(intChoice) + " "
|
||||
print ""
|
||||
|
||||
choices2 = askChoices("Choices 2", "Please choose one or more of the following.",
|
||||
[ 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 ], ["Part 1", "Part 2", "Part 3", "Part 4", "Part 5", "Part 6" ])
|
||||
print "Choices 2: "
|
||||
for intChoice in choices2:
|
||||
print str(intChoice) + " "
|
||||
print ""
|
||||
|
||||
yesOrNo = askYesNo("yes or no", "is this a yes/no question?")
|
||||
print "Yes or No? " + str(yesOrNo)
|
||||
|
||||
except IllegalArgumentException as error:
|
||||
Msg.warn(self, "Error during headless processing: " + error.toString())
|
||||
except CancelledException:
|
||||
print "Cancelled"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Shows how to run a script on all of the programs within the current project.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
# NOTE: Script will only process unversioned and checked-out files.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from ghidra.app.script import GhidraState
|
||||
from ghidra.framework.model import *
|
||||
from ghidra.program.database import ProgramContentHandler
|
||||
from ghidra.program.model.listing import Program
|
||||
from ghidra.util.exception import CancelledException
|
||||
from ghidra.util.exception import VersionException
|
||||
|
||||
from java.io import IOException
|
||||
|
||||
# The script referenced in the following line should be replaced with the script to be called
|
||||
SUBSCRIPT_NAME = "AddCommentToProgramScript.java"
|
||||
|
||||
def recurseProjectFolder(domainFolder):
|
||||
files = domainFolder.getFiles()
|
||||
for domainFile in files:
|
||||
processDomainFile(domainFile)
|
||||
folders = domainFolder.getFolders()
|
||||
for folder in folders:
|
||||
recurseProjectFolder(folder)
|
||||
|
||||
def processDomainFile(domainFile):
|
||||
if not ProgramContentHandler.PROGRAM_CONTENT_TYPE == domainFile.getContentType():
|
||||
return # skip non-Program files
|
||||
if domainFile.isVersioned() and not domainFile.isCheckedOut():
|
||||
println("WARNING! Skipping versioned file - not checked-out: " + domainFile.getPathname())
|
||||
program = None
|
||||
consumer = java.lang.Object()
|
||||
try:
|
||||
program = domainFile.getDomainObject(consumer, True, False, monitor)
|
||||
processProgram(program)
|
||||
|
||||
except VersionException:
|
||||
println("ERROR! Failed to process file due to upgrade issue: " + domainFile.getPathname())
|
||||
|
||||
finally:
|
||||
if program is not None:
|
||||
program.release(consumer)
|
||||
|
||||
def processProgram(program):
|
||||
"""Do you program work here """
|
||||
println("Processing: " + program.getDomainFile().getPathname())
|
||||
monitor.setMessage("Processing: " + program.getDomainFile().getName())
|
||||
id = program.startTransaction("Batch Script Transaction")
|
||||
try:
|
||||
newState = GhidraState(state.getTool(), state.getProject(), program, None, None, None)
|
||||
runScript(SUBSCRIPT_NAME, newState)
|
||||
|
||||
except Exception:
|
||||
printerr("ERROR! Exception occurred while processing file: " + program.getDomainFile().getPathname())
|
||||
printerr(" " + Exception.getMessage())
|
||||
e.printStackTrace()
|
||||
|
||||
finally:
|
||||
program.endTransaction(id, True)
|
||||
|
||||
# ...save any changes
|
||||
program.save("Changes made by script: " + SUBSCRIPT_NAME, monitor)
|
||||
|
||||
if currentProgram is not None:
|
||||
popup("This script should be run from a tool with no open programs")
|
||||
exit()
|
||||
|
||||
project = state.getProject()
|
||||
projectData = project.getProjectData()
|
||||
rootFolder = projectData.getRootFolder()
|
||||
recurseProjectFolder(rootFolder)
|
||||
24
Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py
Normal file
24
Ghidra/Features/Jython/ghidra_scripts/CallAnotherScriptPy.py
Normal file
@@ -0,0 +1,24 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of a script calling another script.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
runScript("HelloWorldScript.java")
|
||||
runScript("HelloWorldPopupScript.java")
|
||||
@@ -0,0 +1,36 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of a script prompting the user for a data type.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.framework.plugintool import PluginTool
|
||||
from ghidra.program.model.data import DataType
|
||||
from ghidra.program.model.data import DataTypeManager
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
|
||||
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType is not None:
|
||||
print "Chosen data type: " + str(dataType)
|
||||
@@ -0,0 +1,48 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example of how to color the listing background
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from ghidra.app.plugin.core.colorizer import ColorizingService
|
||||
from ghidra.app.script import GhidraScript
|
||||
from ghidra.program.model.address import Address
|
||||
from ghidra.program.model.address import AddressSet
|
||||
|
||||
from java.awt import Color
|
||||
|
||||
service = state.getTool().getService(ColorizingService)
|
||||
if service is None:
|
||||
print "Can't find ColorizingService service"
|
||||
if currentSelection is not None:
|
||||
service.setBackgroundColor(currentSelection, Color(255, 200, 200))
|
||||
elif currentAddress is not None:
|
||||
service.setBackgroundColor(currentAddress, currentAddress, Color(255, 200, 200))
|
||||
else:
|
||||
print "No selection or current address to color"
|
||||
|
||||
anotherAddress = currentAddress.add(10)
|
||||
setBackgroundColor(anotherAddress, Color.YELLOW)
|
||||
|
||||
# create an address set with values you want to change
|
||||
addresses = AddressSet()
|
||||
addresses.add(currentAddress.add(10))
|
||||
addresses.add(currentAddress.add(11))
|
||||
addresses.add(currentAddress.add(12))
|
||||
setBackgroundColor(addresses, Color(100, 100, 200))
|
||||
@@ -0,0 +1,43 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example using the Python string formatting.
|
||||
# See FormatExampleScript.java for examples of using the printf() method.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from time import *
|
||||
import java.util.Calendar
|
||||
|
||||
print "The %s jumped over the %s" % ("cow", "moon")
|
||||
|
||||
print "The %s jumped over the %s " % ("cow", "moon") + strftime("%X")
|
||||
|
||||
print "The %s jumped over the %s - timestamp: %s" % ("cow", "moon", strftime("%c %Z"))
|
||||
|
||||
print "The %s jumped over the %s at %s on %s" % ("cow", "moon", strftime("%I:%M%p"), strftime("%A, %b %d"))
|
||||
|
||||
print "Padding: %03d" % (1)
|
||||
|
||||
print "Hex: 0x%x" % (10)
|
||||
|
||||
print "Left-justified: %-10d" % (1)
|
||||
print "Right-justified: %10d" % (1)
|
||||
|
||||
print "String fill: '%10s'" % ("Fill")
|
||||
print "String fill, left justified: '%-10s'" % ("Fill")
|
||||
61
Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py
Normal file
61
Ghidra/Features/Jython/ghidra_scripts/ImportSymbolsScript.py
Normal file
@@ -0,0 +1,61 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Imports a text file containing symbol definitions, with a maximum of one symbol defined per line. Each symbol definition is in the form of "symbol_name address function_or_label", where "symbol_name" is the name of the symbol, "address" is the address of the symbol in one of the forms listed below, and "function_or_label" is either "f" or "l", with "f" indicating that a function is to be created and "l" indicating that a label is to be created.
|
||||
# Address formats are the same as those that can be used with the "Go to address" function. For example:
|
||||
# - 1234abcd
|
||||
# - 0x1234abcd
|
||||
# - ADDRESS_SPACE:1234abcd
|
||||
# - ADDRESS_SPACE:0x1234abcd
|
||||
# - MEMORY_REGION:1234abcd
|
||||
# - MEMORY_REGION:0x1234abcd
|
||||
# Omitting the address space or memory region specifier from the address will result in the function or label being created in the default address space.
|
||||
# @author unkown; edited by matedealer <git@matedealer.de>
|
||||
# @category Data
|
||||
#
|
||||
|
||||
from ghidra.program.model.symbol.SourceType import *
|
||||
import string
|
||||
|
||||
functionManager = currentProgram.getFunctionManager()
|
||||
|
||||
f = askFile("Give me a file to open", "Go baby go!")
|
||||
|
||||
for line in file(f.absolutePath): # note, cannot use open(), since that is in GhidraScript
|
||||
pieces = line.split()
|
||||
|
||||
name = pieces[0]
|
||||
address = toAddr(pieces[1])
|
||||
|
||||
try:
|
||||
function_or_label = pieces[2]
|
||||
except IndexError:
|
||||
function_or_label = "l"
|
||||
|
||||
|
||||
if function_or_label == "f":
|
||||
func = functionManager.getFunctionAt(address)
|
||||
|
||||
if func is not None:
|
||||
old_name = func.getName()
|
||||
func.setName(name, USER_DEFINED)
|
||||
print("Renamed function {} to {} at address {}".format(old_name, name, address))
|
||||
else:
|
||||
func = createFunction(address, name)
|
||||
print("Created function {} at address {}".format(name, address))
|
||||
|
||||
else:
|
||||
print("Created label {} at address {}".format(name, address))
|
||||
createLabel(address, name, False)
|
||||
20
Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py
Normal file
20
Ghidra/Features/Jython/ghidra_scripts/PrintNonZeroPurge.py
Normal file
@@ -0,0 +1,20 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Prints out all the functions in the program that have a non-zero stack purge size
|
||||
|
||||
for func in currentProgram.getFunctionManager().getFunctions(currentProgram.evaluateAddress("0"), 1):
|
||||
if func.getStackPurgeSize() != 0:
|
||||
print "Function", func, "at", func.getEntryPoint(), "has nonzero purge size", func.getStackPurgeSize()
|
||||
@@ -0,0 +1,36 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Writes properties to the tool.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
|
||||
from ghidra.framework.options import Options
|
||||
from ghidra.framework.plugintool import PluginTool
|
||||
|
||||
tool = state.getTool()
|
||||
options = tool.getOptions("name of my script")
|
||||
|
||||
fooString = options.getString("foo", None)
|
||||
|
||||
if fooString is not None : #does not exist in tool options
|
||||
fooString = askString("enter foo", "what value for foo:")
|
||||
if fooString is not None :
|
||||
options.setString("foo", fooString)
|
||||
|
||||
popup(fooString)
|
||||
@@ -0,0 +1,33 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of being imported by a Ghidra Python script/module
|
||||
# @category: Examples.Python
|
||||
|
||||
# The following line will fail if this module is imported from external_module_caller.py,
|
||||
# because only the script that gets directly launched by Ghidra inherits fields and methods
|
||||
# from the GhidraScript/FlatProgramAPI.
|
||||
try:
|
||||
print currentProgram.getName()
|
||||
except NameError:
|
||||
print "Failed to get the program name"
|
||||
|
||||
# The Python module that Ghidra directly launches is always called __main__. If we import
|
||||
# everything from that module, this module will behave as if Ghidra directly launched it.
|
||||
from __main__ import *
|
||||
|
||||
# The below method call should now work
|
||||
print currentProgram.getName()
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of importing an external Ghidra Python module
|
||||
# @category: Examples.Python
|
||||
|
||||
# Import the external module that wants to access the Ghidra scripting API.
|
||||
# NOTE: see external_module_callee.py for additional tips.
|
||||
import external_module_callee
|
||||
77
Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py
Normal file
77
Ghidra/Features/Jython/ghidra_scripts/ghidra_basics.py
Normal file
@@ -0,0 +1,77 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of basic Ghidra scripting in Python
|
||||
# @category: Examples.Python
|
||||
|
||||
# Get info about the current program
|
||||
print
|
||||
print "Program Info:"
|
||||
program_name = currentProgram.getName()
|
||||
creation_date = currentProgram.getCreationDate()
|
||||
language_id = currentProgram.getLanguageID()
|
||||
compiler_spec_id = currentProgram.getCompilerSpec().getCompilerSpecID()
|
||||
print "%s: %s_%s (%s)\n" % (program_name, language_id, compiler_spec_id, creation_date)
|
||||
|
||||
# Get info about the current program's memory layout
|
||||
print "Memory layout:"
|
||||
print "Imagebase: " + hex(currentProgram.getImageBase().getOffset())
|
||||
for block in getMemoryBlocks():
|
||||
start = block.getStart().getOffset()
|
||||
end = block.getEnd().getOffset()
|
||||
print "%s [start: 0x%x, end: 0x%x]" % (block.getName(), start, end)
|
||||
print
|
||||
|
||||
# Get the current program's function names
|
||||
function = getFirstFunction()
|
||||
while function is not None:
|
||||
print function.getName()
|
||||
function = getFunctionAfter(function)
|
||||
print
|
||||
|
||||
# Get the address of the current program's current location
|
||||
print "Current location: " + hex(currentLocation.getAddress().getOffset())
|
||||
|
||||
# Get some user input
|
||||
val = askString("Hello", "Please enter a value")
|
||||
print val
|
||||
|
||||
# Output to a popup window
|
||||
popup(val)
|
||||
|
||||
# Add a comment to the current program
|
||||
minAddress = currentProgram.getMinAddress()
|
||||
listing = currentProgram.getListing()
|
||||
codeUnit = listing.getCodeUnitAt(minAddress)
|
||||
codeUnit.setComment(codeUnit.PLATE_COMMENT, "This is an added comment!")
|
||||
|
||||
# Get a data type from the user
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType != None: print "Chosen data type: " + str(dataType)
|
||||
|
||||
# Report progress to the GUI. Do this in all script loops!
|
||||
import time
|
||||
monitor.initialize(10)
|
||||
for i in range(10):
|
||||
monitor.checkCanceled() # check to see if the user clicked cancel
|
||||
time.sleep(1) # pause a bit so we can see progress
|
||||
monitor.incrementProgress(1) # update the progress
|
||||
monitor.setMessage("Working on " + str(i)) # update the status message
|
||||
53
Ghidra/Features/Jython/ghidra_scripts/jython_basics.py
Normal file
53
Ghidra/Features/Jython/ghidra_scripts/jython_basics.py
Normal file
@@ -0,0 +1,53 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of Jython-specific functionality
|
||||
# @category: Examples.Python
|
||||
|
||||
# Using Java data structures from Jython
|
||||
python_list = [1, 2, 3]
|
||||
java_list = java.util.LinkedList(java.util.Arrays.asList(1, 2, 3))
|
||||
print str(type(python_list))
|
||||
print str(type(java_list))
|
||||
|
||||
# Importing Java packages for simpler Java calls
|
||||
from java.util import LinkedList, Arrays
|
||||
python_list = [1, 2, 3]
|
||||
java_list = LinkedList(Arrays.asList(1, 2, 3))
|
||||
print str(type(python_list))
|
||||
print str(type(java_list))
|
||||
|
||||
# Python adds helpful syntax to Java data structures
|
||||
print python_list[0]
|
||||
print java_list[0] # can't normally do this in java
|
||||
print java_list[0:2] # can't normally do this in java
|
||||
|
||||
# Iterate over Java collection the Python way
|
||||
for entry in java_list:
|
||||
print entry
|
||||
|
||||
# "in" keyword compatibility
|
||||
print str(3 in java_list)
|
||||
|
||||
# Create GUI with Java Swing
|
||||
from javax.swing import JFrame
|
||||
frame = JFrame() # don't call constructor with "new"
|
||||
frame.setSize(400,400)
|
||||
frame.setLocation(200, 200)
|
||||
frame.setTitle("Jython JFrame")
|
||||
frame.setVisible(True)
|
||||
|
||||
# Use JavaBean properties in constructor with keyword arguments!
|
||||
JFrame(title="Super Jython JFrame", size=(400,400), visible=True)
|
||||
117
Ghidra/Features/Jython/ghidra_scripts/python_basics.py
Normal file
117
Ghidra/Features/Jython/ghidra_scripts/python_basics.py
Normal file
@@ -0,0 +1,117 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of basic Python
|
||||
# @category: Examples.Python
|
||||
|
||||
# Python data types
|
||||
my_int = 32
|
||||
print my_int
|
||||
print hex(my_int)
|
||||
|
||||
my_bool = True
|
||||
print my_bool
|
||||
print not my_bool
|
||||
|
||||
my_string = 'this is a string'
|
||||
print my_string
|
||||
print my_string[:4]
|
||||
print my_string[-5:]
|
||||
print type(my_string)
|
||||
|
||||
my_list = ["a", 2, 5.3, my_string]
|
||||
print my_list
|
||||
print my_list[1]
|
||||
print my_list[1:2]
|
||||
print my_list + [1, 2, 3]
|
||||
print type(my_list)
|
||||
|
||||
my_tuple = (1, 2, 3)
|
||||
print my_tuple
|
||||
print my_tuple + (4,)
|
||||
print type(my_tuple)
|
||||
|
||||
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
|
||||
print my_dictionary["key3"]
|
||||
print type(my_dictionary)
|
||||
|
||||
my_null = None
|
||||
print my_null
|
||||
print type(my_null)
|
||||
|
||||
# Python conditionals
|
||||
if len(my_string) == 16:
|
||||
print "length of my_string is 16!"
|
||||
|
||||
if 4 not in my_list:
|
||||
print "4 is not in my_list!"
|
||||
|
||||
if type(my_dictionary) == type(dict):
|
||||
print "my_dictionary is a dictionary!"
|
||||
|
||||
if my_null is None and 2 + 2 == 4:
|
||||
print "my_null is None and 2 + 2 == 4!"
|
||||
|
||||
# Python loops
|
||||
for i in range(1, 10):
|
||||
print i
|
||||
|
||||
for letter in "word":
|
||||
print letter
|
||||
|
||||
for element in [100, 200, 300]:
|
||||
print element
|
||||
|
||||
for key in my_dictionary:
|
||||
print "%s:%s" % (key, my_dictionary[key])
|
||||
|
||||
i = 5
|
||||
while i < 8:
|
||||
print i
|
||||
i += 1
|
||||
|
||||
# Python functions
|
||||
def factorial(n):
|
||||
if n == 0:
|
||||
return 1
|
||||
return n * factorial(n-1)
|
||||
|
||||
i = 4
|
||||
print str(i) + "! = " + str(factorial(4))
|
||||
|
||||
# Python exceptions
|
||||
def error_function():
|
||||
raise IOError("An IO error occurred!")
|
||||
|
||||
try:
|
||||
error_function()
|
||||
print "I won't print"
|
||||
except IOError as e:
|
||||
print e.message
|
||||
|
||||
# Python class
|
||||
class Employee:
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
def getId(self):
|
||||
return self.id
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
e = Employee(5555, "Snoopy")
|
||||
print e.getName()
|
||||
190
Ghidra/Features/Jython/jython-src/ghidradoc.py
Normal file
190
Ghidra/Features/Jython/jython-src/ghidradoc.py
Normal file
@@ -0,0 +1,190 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
"""
|
||||
Ties the Ghidra documentation into the builtin Python help.
|
||||
"""
|
||||
|
||||
import __builtin__
|
||||
import java
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from ghidra.framework import Application
|
||||
from ghidra.util import SystemUtilities
|
||||
|
||||
class _Helper:
|
||||
def __init__(self):
|
||||
self.orig_help = __builtin__.help
|
||||
if SystemUtilities.isInHeadlessMode():
|
||||
# ./pythonRun scenario
|
||||
self.msg = "\nExample workflow:\n"
|
||||
self.msg += " # Import headless analyzer\n"
|
||||
self.msg += " from ghidra.app.util.headless import HeadlessAnalyzer\n\n"
|
||||
self.msg += " # View HeadlessAnalyzer API\n"
|
||||
self.msg += " help(HeadlessAnalyzer)\n\n"
|
||||
self.msg += " # Get a HeadlessAnalyzer instance\n"
|
||||
self.msg += " headless = HeadlessAnalyzer.getInstance()\n\n"
|
||||
self.msg += " # Get headless options\n"
|
||||
self.msg += " options = headless.getOptions()\n\n"
|
||||
self.msg += " # View HeadlessOptions API and set options accordingly\n"
|
||||
self.msg += " help(options)\n\n"
|
||||
self.msg += " # View processLocal method API\n"
|
||||
self.msg += " help(headless.processLocal)\n\n"
|
||||
self.msg += " # Perform headless processing\n"
|
||||
self.msg += " headless.processLocal(...)\n\n"
|
||||
else:
|
||||
# PythonPlugin scenario
|
||||
self.msg = "Press 'F1' for usage instructions"
|
||||
|
||||
def __call__(self, param=None):
|
||||
|
||||
def get_class_and_method(param):
|
||||
if param is None and not SystemUtilities.isInHeadlessMode():
|
||||
# Enable help() in PythonPlugin scenario to show help for GhidraScript
|
||||
return "ghidra.app.script.GhidraScript", None
|
||||
class_name = None
|
||||
method_name = None
|
||||
if type(param) in [type(1), type(1j), type(1L), type(1.0), type(None), type(True), type([]), type({}), type(()), type({1})]:
|
||||
# These are instances of builtin types, so skip
|
||||
pass
|
||||
elif type(param) == type(str):
|
||||
# These are builtin Python types, so skip
|
||||
pass
|
||||
elif type(param) == type(str.split):
|
||||
# These are python functions, so skip
|
||||
pass
|
||||
elif type(param) == type(java):
|
||||
# These are java packages, which we don't don't document, so skip
|
||||
pass
|
||||
elif type(param) == type(java.lang.Object):
|
||||
# This is a java class, so extract its class name
|
||||
match = re.search("'(.*)'", str(param))
|
||||
if match is not None:
|
||||
class_name = match.group(1)
|
||||
elif type(param) == type(java.lang.Object().toString):
|
||||
# This is a java method, so extract its class name and method name
|
||||
tokens = str(param).split(" ")[2].split(".")
|
||||
class_name = ".".join(tokens[:-1])
|
||||
method_name = tokens[-1]
|
||||
else:
|
||||
# Assuming this is a java object, so extract its class name
|
||||
match = re.search("'(.*)'", str(type(param)))
|
||||
if match is not None:
|
||||
class_name = match.group(1)
|
||||
return class_name, method_name
|
||||
|
||||
def get_jsondoc(class_name):
|
||||
jsondoc = None
|
||||
try:
|
||||
root = Application.getApplicationRootDirectory().getFile(False).getParentFile().getAbsolutePath()
|
||||
javadoc_zip_name = "GhidraAPI_javadoc.zip"
|
||||
if SystemUtilities.isInDevelopmentMode():
|
||||
javadoc_zip = root + "/build/tmp/" + javadoc_zip_name
|
||||
else:
|
||||
javadoc_zip = root + "/docs/" + javadoc_zip_name
|
||||
if os.path.exists(javadoc_zip):
|
||||
json_path = "api/" + class_name.replace('.', '/') + '.json'
|
||||
with zipfile.ZipFile(javadoc_zip, "r").open(json_path) as f:
|
||||
jsondoc = json.load(f)
|
||||
except (IOError, KeyError) as e:
|
||||
pass
|
||||
return jsondoc
|
||||
|
||||
def format_class(cls):
|
||||
sig = "class " + cls["name"] + "\n"
|
||||
if "extends" in cls:
|
||||
sig += " extends " + cls["extends"] + "\n"
|
||||
implements = ""
|
||||
for interface in cls["implements"]:
|
||||
if len(implements) > 0:
|
||||
implements += ", "
|
||||
implements += interface
|
||||
if len(implements) > 0:
|
||||
sig += " implements " + implements + " \n"
|
||||
sig += "\n" + cls["comment"]
|
||||
return sig
|
||||
|
||||
def format_field(field):
|
||||
sig = "%s %s" % (field["type_long"], field["name"])
|
||||
if field["static"]:
|
||||
sig = "static " + sig
|
||||
if field["constant_value"]:
|
||||
sig += " = " + field["constant_value"]
|
||||
sig += "\n"
|
||||
desc = " %s\n" % (field["comment"]) if len(field["comment"]) > 0 else ""
|
||||
return sig + desc
|
||||
|
||||
def format_method(method):
|
||||
paramsig = ""
|
||||
args = ""
|
||||
for param in method["params"]:
|
||||
if len(paramsig) > 0:
|
||||
paramsig += ", "
|
||||
paramsig += "%s %s" % (param["type_short"], param["name"])
|
||||
args += " @param %s (%s): %s\n" % (param["name"], param["type_long"], param["comment"])
|
||||
throws = ""
|
||||
for exception in method["throws"]:
|
||||
throws += " @throws %s: %s\n" % (exception["type_short"], exception["comment"])
|
||||
sig = "%s %s(%s)\n" % (method["return"]["type_short"], method["name"], paramsig)
|
||||
if method["static"]:
|
||||
sig = "static " + sig
|
||||
desc = " %s\n\n" % (method["comment"]) if len(method["comment"]) > 0 else ""
|
||||
ret = ""
|
||||
if method["return"]["type_short"] != "void":
|
||||
ret = " @return %s: %s\n" % (method["return"]["type_long"], method["return"]["comment"])
|
||||
return sig + desc + args + ret + throws
|
||||
|
||||
class_name, method_name = get_class_and_method(param)
|
||||
if class_name is None:
|
||||
self.orig_help(param)
|
||||
else:
|
||||
try_again = True
|
||||
while try_again:
|
||||
try_again = False
|
||||
print "Searching API for " + class_name + ("" if method_name is None else "." + method_name + "()") + "..."
|
||||
jsondoc = get_jsondoc(class_name)
|
||||
if jsondoc is None:
|
||||
print "No API found for " + class_name
|
||||
elif method_name is None:
|
||||
print "#####################################################"
|
||||
print format_class(jsondoc)
|
||||
print "#####################################################\n"
|
||||
for field in jsondoc["fields"]:
|
||||
print format_field(field)
|
||||
print "-----------------------------------------------------"
|
||||
for method in jsondoc["methods"]:
|
||||
print format_method(method)
|
||||
print "-----------------------------------------------------"
|
||||
else:
|
||||
found_method = False
|
||||
for method in jsondoc["methods"]:
|
||||
if method["name"] == method_name:
|
||||
print "-----------------------------------------------------"
|
||||
print format_method(method)
|
||||
print "-----------------------------------------------------"
|
||||
found_method = True
|
||||
if not found_method:
|
||||
# The method may be inherited, so check for a super class and try again
|
||||
if "extends" in jsondoc:
|
||||
class_name = jsondoc["extends"]
|
||||
try_again = True
|
||||
|
||||
def __repr__(self):
|
||||
return self.msg
|
||||
|
||||
__builtin__.help = _Helper()
|
||||
530
Ghidra/Features/Jython/jython-src/introspect.py
Normal file
530
Ghidra/Features/Jython/jython-src/introspect.py
Normal file
@@ -0,0 +1,530 @@
|
||||
## ###
|
||||
# IP: LGPL 2.1
|
||||
##
|
||||
"""
|
||||
Provides a variety of introspective-type support functions for
|
||||
things like call tips and command auto completion.
|
||||
|
||||
NOTE: this file is a modification of Patrick O'Brien's version 1.62
|
||||
"""
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
import cStringIO
|
||||
import inspect
|
||||
import sys
|
||||
import tokenize
|
||||
import types
|
||||
import __main__
|
||||
from ghidra.jython import JythonCodeCompletionFactory
|
||||
import java # needed for java.lang.Class
|
||||
|
||||
try:
|
||||
True
|
||||
except NameError:
|
||||
True = 1==1
|
||||
False = 1==0
|
||||
|
||||
#from java.lang.System.out import println
|
||||
|
||||
def getAutoCompleteList(command='', locals=None, includeMagic=1,
|
||||
includeSingle=1, includeDouble=1):
|
||||
"""Return list of auto-completion tuples for command.
|
||||
|
||||
First entry is the possible completions, and second entry is the
|
||||
of actual string that should be added to do the completion.
|
||||
|
||||
The list of options will be based on the locals namespace."""
|
||||
attributes = []
|
||||
object = None
|
||||
# Get the proper chunk of code from the command.
|
||||
#root = getRoot(command, terminator='.')
|
||||
# and get the part of the completion we should filter on
|
||||
(root, filter) = getRootAndFilter(command, terminator='.')
|
||||
if root:
|
||||
jump_past_period = 1
|
||||
else:
|
||||
jump_past_period = 0
|
||||
|
||||
#println("root='" + root + "'")
|
||||
#println("filter='" + filter + "'")
|
||||
|
||||
if not root:
|
||||
# top-level?
|
||||
attributes = locals
|
||||
else:
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
#print "could not eval(", root, "):", sys.exc_info()[0]
|
||||
pass
|
||||
else:
|
||||
attributes = getAttributeNames(object, includeMagic,
|
||||
includeSingle, includeDouble)
|
||||
completion_list = []
|
||||
for attribute in attributes:
|
||||
if attribute.lower().startswith(filter.lower()):
|
||||
try:
|
||||
if object is not None:
|
||||
pyObj = getattr(object, attribute)
|
||||
else:
|
||||
pyObj = locals[attribute]
|
||||
completion_list.append(JythonCodeCompletionFactory.
|
||||
newCodeCompletion(attribute,
|
||||
attribute,
|
||||
pyObj,
|
||||
filter))
|
||||
except:
|
||||
# hmm, problem evaluating? Examples of this include
|
||||
# inner classes, e.g. access$0, which aren't valid Python
|
||||
# anyway
|
||||
pass
|
||||
completion_list.sort(compare_completions)
|
||||
return completion_list
|
||||
|
||||
def compare_completions(comp1, comp2):
|
||||
return cmp(comp1.description, comp2.description)
|
||||
|
||||
def getAttributeNames(object, includeMagic=1, includeSingle=1,
|
||||
includeDouble=1):
|
||||
"""Return list of unique attributes, including inherited, for object."""
|
||||
attributes = []
|
||||
dict = {}
|
||||
if not hasattrAlwaysReturnsTrue(object):
|
||||
# Add some attributes that don't always get picked up. If
|
||||
# they don't apply, they'll get filtered out at the end
|
||||
attributes += ['__bases__', '__class__', '__dict__', '__name__',
|
||||
'func_closure', 'func_code', 'func_defaults',
|
||||
'func_dict', 'func_doc', 'func_globals', 'func_name']
|
||||
if includeMagic:
|
||||
try: attributes += object._getAttributeNames()
|
||||
except: pass
|
||||
# Get all attribute names.
|
||||
attrdict = getAllAttributeNames(object)
|
||||
for attrlist in attrdict.values():
|
||||
attributes += attrlist
|
||||
# Remove duplicates from the attribute list.
|
||||
for item in attributes:
|
||||
dict[item] = None
|
||||
attributes = dict.keys()
|
||||
attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
|
||||
if not includeSingle:
|
||||
attributes = filter(lambda item: item[0]!='_' \
|
||||
or item[1]=='_', attributes)
|
||||
if not includeDouble:
|
||||
attributes = filter(lambda item: item[:2]!='__', attributes)
|
||||
#print "attributes currently", attributes
|
||||
# Make sure we haven't picked up any bogus attributes somehow.
|
||||
#attributes = [attribute for attribute in attributes \
|
||||
# if hasattr(object, attribute)]
|
||||
retval = []
|
||||
for attribute in attributes:
|
||||
#print "checking", attribute
|
||||
try:
|
||||
if hasattr(object, attribute):
|
||||
retval += [attribute]
|
||||
except:
|
||||
# yes Virginia, hasattr() can fail in Jython 2.2 because trying
|
||||
# to get __doc__ on a builtin class results in NullPointerException
|
||||
pass
|
||||
return retval
|
||||
|
||||
def hasattrAlwaysReturnsTrue(object):
|
||||
return hasattr(object, 'bogu5_123_aTTri8ute')
|
||||
|
||||
def getAllAttributeNames(object):
|
||||
"""Return dict of all attributes, including inherited, for an object.
|
||||
|
||||
Recursively walk through a class and all base classes.
|
||||
"""
|
||||
attrdict = {} # (object, technique, count): [list of attributes]
|
||||
# !!!
|
||||
# Do Not use hasattr() as a test anywhere in this function,
|
||||
# because it is unreliable with the remote objects: xmlrpc, soap, etc.
|
||||
# They always return true for hasattr().
|
||||
# !!!
|
||||
try:
|
||||
# Yes, this can fail if object is an instance of a class with
|
||||
# __str__ (or __repr__) having a bug or raising an
|
||||
# exception. :-(
|
||||
key = str(object)
|
||||
except:
|
||||
key = 'anonymous'
|
||||
# Wake up sleepy object - a hack for ZODB objects in "ghost" state.
|
||||
try:
|
||||
wakeupcall = dir(object)
|
||||
del wakeupcall
|
||||
except:
|
||||
pass
|
||||
# Get attributes available through the normal convention
|
||||
try:
|
||||
attributes = dir(object)
|
||||
attrdict[(key, 'dir', len(attributes))] = attributes
|
||||
except:
|
||||
# sadly, this fails for PyReflectedFunctions
|
||||
pass
|
||||
# Get attributes rom the object's dictionary, if it has one.
|
||||
try:
|
||||
attributes = object.__dict__.keys()
|
||||
attributes.sort()
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
attrdict[(key, '__dict__', len(attributes))] = attributes
|
||||
# For a class instance, get the attributes for the class.
|
||||
try:
|
||||
klass = object.__class__
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
if klass is object:
|
||||
# Break a circular reference. This happens with extension
|
||||
# classes.
|
||||
#print "breaking circular reference to self"
|
||||
pass
|
||||
# this extra check added for Jython 2.2.1 to break circular recursion
|
||||
elif klass is not java.lang.Class:
|
||||
# print "calling update from", object, "with", klass
|
||||
attrdict.update(getAllAttributeNames(klass))
|
||||
# Also get attributes from any and all parent classes.
|
||||
try:
|
||||
bases = object.__bases__
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
if isinstance(bases, types.TupleType):
|
||||
# needed for Jython 2.2?
|
||||
halt_type = type(types.TypeType)
|
||||
for base in bases:
|
||||
if type(base) is types.TypeType \
|
||||
or type(base) is halt_type:
|
||||
# Break a circular reference. Happens in Python 2.2.
|
||||
#print "breaking TypeType circular reference"
|
||||
pass
|
||||
else:
|
||||
# print "calling update (better not be 'type') with", base
|
||||
attrdict.update(getAllAttributeNames(base))
|
||||
return attrdict
|
||||
|
||||
def getCallTip(command='', locals=None):
|
||||
"""For a command, return a tuple of object name, argspec, tip text.
|
||||
|
||||
The call tip information will be based on the locals namespace."""
|
||||
calltip = ('', '', '') # object name, argspec, tip text.
|
||||
# Get the proper chunk of code from the command.
|
||||
root = getRoot(command, terminator='(')
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
#print "could not eval(", root, "):", sys.exc_info()[0]
|
||||
return calltip
|
||||
name = ''
|
||||
object, dropSelf = getBaseObject(object)
|
||||
try:
|
||||
name = object.__name__
|
||||
except AttributeError:
|
||||
pass
|
||||
tip1 = ''
|
||||
argspec = ''
|
||||
if inspect.isbuiltin(object):
|
||||
# Builtin function don't have an argspec that we can get.
|
||||
pass
|
||||
elif inspect.isfunction(object):
|
||||
# tip1 is a string like: "getCallTip(command='', locals=None)"
|
||||
argspec = apply(inspect.formatargspec, inspect.getargspec(object))
|
||||
if dropSelf:
|
||||
# Thh first parameter to a method is a reference to an
|
||||
# instance, usually coded as "self", and is usually passed
|
||||
# automatically by Python; therefore we want to drop it.
|
||||
temp = argspec.split(',')
|
||||
if len(temp) == 1: # No other arguments.
|
||||
argspec = '()'
|
||||
else: # Drop the first argument.
|
||||
argspec = '(' + ','.join(temp[1:]).lstrip()
|
||||
tip1 = name + argspec
|
||||
doc = ''
|
||||
if callable(object):
|
||||
try:
|
||||
doc = inspect.getdoc(object)
|
||||
except:
|
||||
# Jython 2.2a1 could throw and exception with getdoc()
|
||||
pass
|
||||
if doc:
|
||||
# tip2 is the first separated line of the docstring, like:
|
||||
# "Return call tip text for a command."
|
||||
# tip3 is the rest of the docstring, like:
|
||||
# "The call tip information will be based on ... <snip>
|
||||
firstline = doc.split('\n')[0].lstrip()
|
||||
if tip1 == firstline:
|
||||
tip1 = ''
|
||||
else:
|
||||
tip1 += '\n\n'
|
||||
docpieces = doc.split('\n\n')
|
||||
tip2 = docpieces[0]
|
||||
tip3 = '\n\n'.join(docpieces[1:])
|
||||
tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
|
||||
else:
|
||||
tip = tip1
|
||||
calltip = (name, argspec[1:-1], tip.strip())
|
||||
return calltip
|
||||
|
||||
def getRoot(command, terminator=None):
|
||||
"""Return the rightmost root portion of an arbitrary Python command.
|
||||
|
||||
Return only the root portion that can be eval()ed without side
|
||||
effect. The command would normally terminate with a '(' or
|
||||
'.'. The terminator and anything after the terminator will be
|
||||
dropped."""
|
||||
command = command.split('\n')[-1]
|
||||
if command.startswith(sys.ps2):
|
||||
command = command[len(sys.ps2):]
|
||||
command = command.lstrip()
|
||||
command = rtrimTerminus(command, terminator)
|
||||
tokens = getTokens(command)
|
||||
if not tokens:
|
||||
return ''
|
||||
if tokens[-1][0] is tokenize.ENDMARKER:
|
||||
# Remove the end marker.
|
||||
del tokens[-1]
|
||||
if not tokens:
|
||||
return ''
|
||||
if terminator == '.' and \
|
||||
(tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
|
||||
# Trap decimals in numbers, versus the dot operator
|
||||
return ''
|
||||
else:
|
||||
# Strip off the terminator.
|
||||
if terminator and command.endswith(terminator):
|
||||
size = 0 - len(terminator)
|
||||
command = command[:size]
|
||||
command = command.rstrip()
|
||||
tokens = getTokens(command)
|
||||
tokens.reverse()
|
||||
line = ''
|
||||
start = None
|
||||
prefix = ''
|
||||
laststring = '.'
|
||||
emptyTypes = ('[]', '()', '{}')
|
||||
for token in tokens:
|
||||
tokentype = token[0]
|
||||
tokenstring = token[1]
|
||||
line = token[4]
|
||||
if tokentype is tokenize.ENDMARKER:
|
||||
continue
|
||||
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
|
||||
and laststring != '.':
|
||||
# We've reached something that's not part of the root.
|
||||
if prefix and line[token[3][1]] != ' ':
|
||||
# If it doesn't have a space after it, remove the prefix.
|
||||
prefix = ''
|
||||
break
|
||||
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
|
||||
or (tokentype is tokenize.OP and tokenstring == '.'):
|
||||
if prefix:
|
||||
# The prefix isn't valid because it comes after a dot.
|
||||
prefix = ''
|
||||
break
|
||||
else:
|
||||
# start represents the last known good point in the line
|
||||
start = token[2][1]
|
||||
elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
|
||||
# Remember, we're working backwards.
|
||||
# So prefix += tokenstring would be wrong.
|
||||
if prefix in emptyTypes and tokenstring in ('[({'):
|
||||
# We've already got an empty type identified so now we
|
||||
# are in a nested situation and we can break out with
|
||||
# what we've got
|
||||
break
|
||||
else:
|
||||
prefix = tokenstring = prefix
|
||||
else:
|
||||
# We've reached something that's not part of the root
|
||||
break
|
||||
laststring= tokenstring
|
||||
if start is None:
|
||||
start = len(line)
|
||||
root = line[start:]
|
||||
if prefix in emptyTypes:
|
||||
# Empty types are safe to be eval()'d and introspected.
|
||||
root = prefix + root
|
||||
return root
|
||||
|
||||
def getRootAndFilter(command, terminator=None):
|
||||
"""Return the rightmost root portion of an arbitrary Python command.
|
||||
Also returns the filter, which is the fragment after the root and
|
||||
terminator.
|
||||
|
||||
Return only the root portion that can be eval()ed without side
|
||||
effect. The command would normally terminate with a '(' or
|
||||
'.'. The terminator and anything after the terminator will be
|
||||
dropped."""
|
||||
command = command.split('\n')[-1]
|
||||
if command.startswith(sys.ps2):
|
||||
command = command[len(sys.ps2):]
|
||||
command = command.lstrip()
|
||||
tokens = getTokens(command)
|
||||
if not tokens:
|
||||
return ('', '')
|
||||
tokens.reverse()
|
||||
root = ""
|
||||
filter = ""
|
||||
terminator_seen = False
|
||||
# initialize matching bracket/paren sets
|
||||
op_mate = {'}': '{',
|
||||
']': '[',
|
||||
')': '('}
|
||||
op_stack = []
|
||||
while len(tokens) > 0:
|
||||
# work backwards through the tokens, starting at the end of the string
|
||||
token = tokens[0]
|
||||
del tokens[0]
|
||||
tokentype = token[0]
|
||||
tokenstring = token[1]
|
||||
line = token[4]
|
||||
if tokentype is tokenize.ENDMARKER:
|
||||
|
||||
#println("Hit end marker; continuing")
|
||||
|
||||
continue
|
||||
if not terminator_seen:
|
||||
if not filter and tokentype in (tokenize.NAME, tokenize.STRING):
|
||||
# okay, we think we've found our filter string
|
||||
filter = tokenstring
|
||||
|
||||
#println("hit filter string '" + filter + "'")
|
||||
|
||||
elif tokenstring == terminator:
|
||||
# hooray, our terminator!
|
||||
terminator_seen = True
|
||||
else:
|
||||
# either we found another token before our already-set
|
||||
# filter string, or we just found a bad token
|
||||
|
||||
#println("No valid tokens found after terminator (at '" +
|
||||
# tokenstring + "'")
|
||||
|
||||
break
|
||||
else:
|
||||
# we've seen the terminator -- continue adding valid tokens
|
||||
# until we hit a token that stops us
|
||||
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER):
|
||||
root = tokenstring + root
|
||||
|
||||
#println("Added to root: '" + root + "'")
|
||||
|
||||
elif tokentype is tokenize.OP:
|
||||
if tokenstring in op_mate.keys():
|
||||
# found a closing bracket/paren
|
||||
op_stack.append(tokenstring)
|
||||
elif tokenstring in op_mate.values():
|
||||
if len(op_stack) < 1 or \
|
||||
tokenstring != op_mate[op_stack.pop()]:
|
||||
# uh-oh, non-matching brackets/parens!
|
||||
break
|
||||
elif len(op_stack) > 0:
|
||||
# weird tokens are okay inside brackets/parens
|
||||
pass
|
||||
elif tokenstring == '.':
|
||||
# dots are okay too!
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
root = tokenstring + root
|
||||
|
||||
#println("Added to root: '" + root + "'")
|
||||
else:
|
||||
# hit a terminating token
|
||||
break
|
||||
return (root, filter)
|
||||
|
||||
def getTokens(command):
|
||||
"""Return list of token tuples for command."""
|
||||
command = str(command) # In case the command is unicode, which fails.
|
||||
f = cStringIO.StringIO(command)
|
||||
# tokens is a list of token tuples, each looking like:
|
||||
# (type, string, (srow, scol), (erow, ecol), line)
|
||||
tokens = []
|
||||
# Can't use list comprehension:
|
||||
# tokens = [token for token in tokenize.generate_tokens(f.readline)]
|
||||
# because of need to append as much as possible before TokenError.
|
||||
try:
|
||||
# This works with Python 2.1.3 (with nested_scopes)
|
||||
def eater(*args):
|
||||
tokens.append(args)
|
||||
tokenize.tokenize_loop(f.readline, eater)
|
||||
except tokenize.TokenError:
|
||||
# This is due to a premature EOF, which we expect since we are
|
||||
# feeding in fragments of Python code
|
||||
pass
|
||||
return tokens
|
||||
|
||||
def rtrimTerminus(command, terminator=None):
|
||||
"""Return command minus anything that follows the final terminator."""
|
||||
if terminator:
|
||||
pieces = command.split(terminator)
|
||||
if len(pieces) > 1:
|
||||
command = terminator.join(pieces[:-1]) + terminator
|
||||
return command
|
||||
|
||||
def getBaseObject(object):
|
||||
"""Return base object and dropSelf indicator for an object."""
|
||||
if inspect.isbuiltin(object):
|
||||
# Builtin function don't have an argspec that we can get.
|
||||
dropSelf = 0
|
||||
elif inspect.ismethod(object):
|
||||
# Get the function from the object otherwise
|
||||
# inspect.getargspec() complains that the object isn't a
|
||||
# Python funciton.
|
||||
try:
|
||||
if object.im_self is None:
|
||||
# This is an unbound method so we do not drop self
|
||||
# from the argpec, sinc ean instance must be passed
|
||||
# as the first arg.
|
||||
dropSelf = 0
|
||||
else:
|
||||
dropSelf = 1
|
||||
object = object.im_func
|
||||
except AttributeError:
|
||||
dropSelf = 0
|
||||
elif inspect.isclass(object):
|
||||
# Get the __init__ method function for the class
|
||||
constructor = getConstructor(object)
|
||||
if constructor is not None:
|
||||
object = constructor
|
||||
dropSelf = 1
|
||||
else:
|
||||
dropSelf = 0
|
||||
elif callable(object):
|
||||
# Get the __call__ method instead
|
||||
try:
|
||||
call_method = object.__call__.im_func
|
||||
if call_method.__name__ == '__call__':
|
||||
# unbound jython method end up here, example: string.index(
|
||||
dropSelf = 0
|
||||
else:
|
||||
object = call_method
|
||||
dropSelf = 1
|
||||
except AttributeError:
|
||||
# unbound python methods end up here
|
||||
dropSelf = 0
|
||||
else:
|
||||
dropSelf = 0
|
||||
return object, dropSelf
|
||||
|
||||
def getConstructor(object):
|
||||
"""Return constructor for class object, or None if there isn't one."""
|
||||
try:
|
||||
return object.__init__.im_func
|
||||
except AttributeError:
|
||||
for base in object.__bases__:
|
||||
constructor = getConstructor(base)
|
||||
if constructor is not None:
|
||||
return constructor
|
||||
return None
|
||||
237
Ghidra/Features/Jython/jython-src/jintrospect.py
Normal file
237
Ghidra/Features/Jython/jython-src/jintrospect.py
Normal file
@@ -0,0 +1,237 @@
|
||||
## ###
|
||||
# IP: LGPL 2.1
|
||||
##
|
||||
"""Extend introspect.py for Java based Jython classes."""
|
||||
|
||||
from introspect import *
|
||||
import string
|
||||
import __builtin__
|
||||
import java # needed for java.lang.Class
|
||||
import org # for org.python.core
|
||||
import ghidra # for JythonCodeCompletionFactory
|
||||
|
||||
__author__ = "Don Coleman <dcoleman@chariotsolutions.com>"
|
||||
|
||||
#def getAutoCompleteList(command='', locals=None, includeMagic=1,
|
||||
# includeSingle=1, includeDouble=1):
|
||||
# """Return list of auto-completion options for command.
|
||||
#
|
||||
# The list of options will be based on the locals namespace."""
|
||||
# attributes = []
|
||||
# # Get the proper chunk of code from the command.
|
||||
# root = getRoot(command, terminator='.')
|
||||
# try:
|
||||
# if locals is not None:
|
||||
# object = eval(root, locals)
|
||||
# else:
|
||||
# object = eval(root)
|
||||
# except:
|
||||
# #print "could not eval(", root, "):", sys.exc_info()[0]
|
||||
# return attributes
|
||||
#
|
||||
# if ispython(object):
|
||||
# # use existing code
|
||||
# attributes = getAttributeNames(object, includeMagic, includeSingle,
|
||||
# includeDouble)
|
||||
# else:
|
||||
# methods = methodsOf(object.__class__)
|
||||
# attributes = [eachMethod.__name__ for eachMethod in methods]
|
||||
#
|
||||
# return attributes
|
||||
#
|
||||
#def methodsOf(clazz):
|
||||
# """Return a list of all the methods in a class"""
|
||||
# classMembers = vars(clazz).values()
|
||||
# methods = [eachMember for eachMember in classMembers
|
||||
# if callable(eachMember)]
|
||||
# for eachBase in clazz.__bases__:
|
||||
# methods.extend(methodsOf(eachBase))
|
||||
# return methods
|
||||
|
||||
def getCallTipJava(command='', locals=None):
|
||||
"""For a command, return a tuple of object name, argspec, tip text.
|
||||
|
||||
The call tip information will be based on the locals namespace."""
|
||||
|
||||
calltip = ('', '', '') # object name, argspec, tip text.
|
||||
|
||||
# Get the proper chunk of code from the command.
|
||||
(root, filter) = getRootAndFilter(command, terminator='(')
|
||||
#java.lang.System.out.println("root=" + root)
|
||||
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
#java.lang.System.err.println("could not eval(" + root + "):" +
|
||||
# str(sys.exc_info()[0]))
|
||||
return calltip
|
||||
|
||||
if ispython(object):
|
||||
# Patrick's code handles Python code
|
||||
# TODO fix in future because getCallTip runs eval() again
|
||||
#java.lang.System.out.println("is a Python object")
|
||||
calltip = getCallTip(command, locals)
|
||||
|
||||
if not calltip[1] and not calltip[2]:
|
||||
# either it's a pure Java object, or we didn't get much from Python's
|
||||
# getCallTip
|
||||
name = ''
|
||||
try:
|
||||
name = object.__name__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
tipList = []
|
||||
argspec = '' # not using argspec for Java
|
||||
|
||||
# if inspect.isbuiltin(object):
|
||||
# # inspect.isbuiltin() fails for Jython
|
||||
# # Can we get the argspec for Jython builtins? We can't in Python.
|
||||
# # YES!
|
||||
# print "is a builtin"
|
||||
# pass
|
||||
# elif inspect.isclass(object):
|
||||
if inspect.isclass(object):
|
||||
# get the constructor(s)
|
||||
# TODO consider getting modifiers since Jython can access
|
||||
# private methods
|
||||
#java.lang.System.out.println("is a class")
|
||||
try:
|
||||
# this will likely fail for pure Python classes
|
||||
constructors = object.getConstructors()
|
||||
for constructor in constructors:
|
||||
paramList = []
|
||||
paramTypes = constructor.getParameterTypes()
|
||||
# paramTypes is an array of classes; we need Strings
|
||||
# TODO consider list comprehension
|
||||
for param in paramTypes:
|
||||
# TODO translate [B to byte[], C to char[], etc.
|
||||
paramList.append(param.__name__)
|
||||
paramString = string.join(paramList, ', ')
|
||||
tip = "%s(%s)" % (constructor.name, paramString)
|
||||
tipList.append(tip)
|
||||
if len(constructors) == 1:
|
||||
plural = ""
|
||||
else:
|
||||
plural = "s"
|
||||
name = "Constructor" + plural + " for " + name + ":"
|
||||
except:
|
||||
pass
|
||||
# if callable(object):
|
||||
# # some Python types are function names as well, like
|
||||
# # type() and file()
|
||||
# argspec = str(object.__call__)
|
||||
# # these don't seem to be very accurate
|
||||
# if hasattr(object.__call__, "maxargs"):
|
||||
# tipList.append("maxargs?: " +
|
||||
# str(object.__call__.maxargs))
|
||||
# if hasattr(object.__call__, "minargs"):
|
||||
# tipList.append("minargs?: " +
|
||||
# str(object.__call__.minargs))
|
||||
# elif inspect.ismethod(object):
|
||||
elif inspect.isroutine(object):
|
||||
#java.lang.System.out.println("is a routine")
|
||||
# method = object
|
||||
# object = method.im_class
|
||||
#
|
||||
# # Java allows overloading so we may have more than one method
|
||||
# methodArray = object.getMethods()
|
||||
#
|
||||
# for eachMethod in methodArray:
|
||||
# if eachMethod.name == method.__name__:
|
||||
# paramList = []
|
||||
# for eachParam in eachMethod.parameterTypes:
|
||||
# paramList.append(eachParam.__name__)
|
||||
#
|
||||
# paramString = string.join(paramList, ', ')
|
||||
#
|
||||
# # create a Python style string a la PyCrust
|
||||
# # we're showing the parameter type rather than the
|
||||
# # parameter name, since that's all we can get
|
||||
# # we need to show multiple methods for overloading
|
||||
# # TODO improve message format
|
||||
# # do we want to show the method visibility?
|
||||
# # how about exceptions?
|
||||
# # note: name, return type and exceptions same for
|
||||
# # EVERY overloaded method
|
||||
#
|
||||
#
|
||||
# tip = "%s(%s) -> %s" % (eachMethod.name, paramString,
|
||||
# eachMethod.returnType)
|
||||
# tipList.append(tip)
|
||||
if hasattr(object, "argslist"):
|
||||
for args in object.argslist:
|
||||
if args is not None:
|
||||
# for now
|
||||
tipList.append(str(args.method))
|
||||
# elif callable(object):
|
||||
# argspec = str(object.__call__)
|
||||
# # these don't seem to be very accurate
|
||||
# if hasattr(object.__call__, "maxargs"):
|
||||
# tipList.append("maxargs?: " + str(object.__call__.maxargs))
|
||||
# if hasattr(object.__call__, "minargs"):
|
||||
# tipList.append("minargs?: " + str(object.__call__.minargs))
|
||||
|
||||
# elif inspect.isfunction(object):
|
||||
# print "is function"
|
||||
|
||||
if (len(tipList) == 0):
|
||||
if hasattr(object, "__name__") and \
|
||||
hasattr(__builtin__, object.__name__):
|
||||
# try to get arguments for any other "old-style" builtin
|
||||
# functions (see __builtin__.java, classDictInit() method)
|
||||
methods = \
|
||||
java.lang.Class.getMethods(org.python.core.__builtin__)
|
||||
for method in methods:
|
||||
if method.name == object.__name__:
|
||||
tipList.append(str(method))
|
||||
argspec = "a built-in Python function"
|
||||
else:
|
||||
# last-ditch: try possible __call__ methods of new-style
|
||||
# objects
|
||||
for possible_call_method in \
|
||||
ghidra.python.PythonCodeCompletionFactory.getCallMethods(object):
|
||||
signature = str(possible_call_method)
|
||||
# clean up the method signature a bit, so it looks sane
|
||||
signature = \
|
||||
signature.replace("$1exposed_", ".").replace(".__call__", "")
|
||||
tipList.append(signature)
|
||||
|
||||
calltip = (name, argspec, string.join(tipList, "\n"))
|
||||
return calltip
|
||||
|
||||
def ispython(object):
|
||||
"""
|
||||
Figure out if this is Python code or Java code..
|
||||
"""
|
||||
pyclass = 0
|
||||
pycode = 0
|
||||
pyinstance = 0
|
||||
|
||||
if inspect.isclass(object):
|
||||
try:
|
||||
object.__doc__
|
||||
pyclass = 1
|
||||
except AttributeError:
|
||||
pyclass = 0
|
||||
elif inspect.ismethod(object):
|
||||
try:
|
||||
# changed for Jython 2.2a1
|
||||
#object.__dict__
|
||||
object.__str__
|
||||
pycode = 1
|
||||
except AttributeError:
|
||||
pycode = 0
|
||||
else: # I guess an instance of an object falls here
|
||||
try:
|
||||
# changed for Jython 2.2a1
|
||||
#object.__dict__
|
||||
object.__str__
|
||||
pyinstance = 1
|
||||
except AttributeError:
|
||||
pyinstance = 0
|
||||
|
||||
return pyclass | pycode | pyinstance
|
||||
48
Ghidra/Features/Jython/jython-src/sitecustomize.py
Normal file
48
Ghidra/Features/Jython/jython-src/sitecustomize.py
Normal file
@@ -0,0 +1,48 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
"""
|
||||
User-supplied customizations go here.
|
||||
"""
|
||||
|
||||
# nice-to-have: place 'java' and 'ghidra' into the local namespace, so you
|
||||
# can do fun things like "dir(java.lang)"
|
||||
import java
|
||||
import ghidra
|
||||
import __main__
|
||||
__main__.java = java
|
||||
__main__.ghidra = ghidra
|
||||
|
||||
# fix Jython "bug": unknown type 'javainstance' or 'javapackage' even though
|
||||
# that is the type Jython gives us if we ask type(<someObject>) or
|
||||
# type(ghidra) (respectively)
|
||||
import __builtin__
|
||||
import org.python.core
|
||||
# changed by Jim 20090528 for Jython 2.5
|
||||
# not sure why I even put these here... the first one might be troublesome
|
||||
#__builtin__.javainstance = org.python.core.PyJavaInstance
|
||||
#__builtin__.javapackage = org.python.core.PyJavaPackage
|
||||
#__builtin__.javaclass = org.python.core.PyJavaClass
|
||||
__builtin__.javainstance = org.python.core.PyObjectDerived
|
||||
__builtin__.javapackage = org.python.core.PyJavaPackage
|
||||
__builtin__.javaclass = org.python.core.PyJavaType
|
||||
|
||||
# changed by Jim 20090528 for Jython 2.5
|
||||
# REMOVED collections stuff
|
||||
# OOPS still need this
|
||||
import sys
|
||||
|
||||
# Ghidra documentation
|
||||
import ghidradoc
|
||||
9
Ghidra/Features/Jython/src/main/help/help/TOC_Source.xml
Normal file
9
Ghidra/Features/Jython/src/main/help/help/TOC_Source.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' encoding='ISO-8859-1' ?>
|
||||
|
||||
<tocroot>
|
||||
<tocref id="Ghidra Functionality">
|
||||
<tocref id="Scripting">
|
||||
<tocdef id="Jython Interpreter" sortgroup="z" text="Jython Interpreter" target="help/topics/Jython/interpreter.html" />
|
||||
</tocref>
|
||||
</tocref>
|
||||
</tocroot>
|
||||
@@ -0,0 +1,171 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Jython Interpreter</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Jython"></A>Jython Interpreter</H1>
|
||||
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> provides a full general-purpose Jython interactive shell
|
||||
and allows you to interact with your current Ghidra session by exposing Ghidra's powerful Java
|
||||
API through the magic of Jython.
|
||||
</P>
|
||||
|
||||
<H2>Environment</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> is configured to run in a similar context as a Ghidra
|
||||
script. Therefore, you immediately have access to variables such as <TT>currentProgram</TT>,
|
||||
<TT>currentSelection</TT>, <TT>currentAddress</TT>, etc without needing to import them.
|
||||
These variables exist as Java objects behind the scenes, but Jython allows you to interact with
|
||||
them through a Python interface, which is similar to Java in some ways.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
As in Java, classes outside of your current package/module need to be explicitly imported.
|
||||
For example, consider the following code snippet:
|
||||
</P>
|
||||
<BR>
|
||||
|
||||
<PRE>
|
||||
<FONT COLOR="GREEN"># Get a data type from the user</FONT>
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType != None: print "Chosen data type: " + str(dataType)
|
||||
</PRE>
|
||||
|
||||
<P>
|
||||
<TT>currentProgram</TT> and <TT>state</TT> are defined within the Ghidra scripting class
|
||||
hierarchy, so nothing has to be explicitly imported before they can be used. However, because
|
||||
the <TT>DataTypeSelectionDialog</TT> class and <TT>AllowedDataType</TT> enum reside in
|
||||
different packages, they must be explicitly imported. Failure to do so will result in a
|
||||
Jython <TT><FONT COLOR="RED">NameError</FONT></TT>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Clear_Interpreter"></A>Clear <IMG border="0" src="images/erase16.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command clears the interpreter's display. Its effect is purely visual.
|
||||
It does not affect the state of the interpreter in any way.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Interrupt_Interpreter"></A>Interrupt <IMG border="0" src="images/dialog-cancel.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command issues a keyboard interrupt to the interpreter, which can be used to interrupt
|
||||
long running commands or loops.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Reset_Interpreter"></A>Reset <IMG border="0" src="images/reload3.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command resets the interpreter, which clears the display and resets all state.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Keybindings</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> supports the following hard-coded keybindings:
|
||||
<UL>
|
||||
<LI><B>(up):</B> Move backward in command stack</LI>
|
||||
<LI><B>(down):</B> Move forward in command stack</LI>
|
||||
<LI><B>TAB:</B> Show code completion window</LI>
|
||||
</UL>
|
||||
|
||||
<P>
|
||||
With the code completion window open:
|
||||
<UL>
|
||||
<LI><B>TAB:</B> Insert currently-selected code completion (if no completion selected, select the first available)</LI>
|
||||
<LI><B>ENTER:</B> Insert selected completion (if any) and close the completion window</LI>
|
||||
<LI><B>(up):</B> Select previous code completion</LI>
|
||||
<LI><B>(down):</B> Select next code completion</LI>
|
||||
<LI><B>ESC:</B> Hide code completion window</LI>
|
||||
</UL>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Copy/Paste</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
Copy and paste from within the Ghidra <I>Jython Interpreter</I> should work as expected for
|
||||
your given environment:
|
||||
<UL>
|
||||
<LI><B>Windows:</B> CTRL+C / CTRL+V</LI>
|
||||
<LI><B>Linux:</B> CTRL+C / CTRL+V</LI>
|
||||
<LI><B>OS X:</B> COMMAND+C / COMMAND+V</LI>
|
||||
</UL>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>API Documentation</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The built-in <TT>help()</TT> 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> </P>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -0,0 +1,559 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
|
||||
import org.python.core.*;
|
||||
import org.python.util.InteractiveInterpreter;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A python interpreter meant for Ghidra's use. Each interpreter you get will have its own
|
||||
* variable space so they should not interfere with each other.
|
||||
* <p>
|
||||
* There is no longer a way to reset an interpreter...it was too complicated to get right.
|
||||
* Instead, you should {@link #cleanup()} your old interpreter and make a new one.
|
||||
*/
|
||||
public class GhidraJythonInterpreter extends InteractiveInterpreter {
|
||||
|
||||
private static boolean pythonInitialized;
|
||||
private static List<PyString> defaultPythonPath;
|
||||
|
||||
private TraceFunction interruptTraceFunction;
|
||||
private PyModule introspectModule;
|
||||
private PyModule builtinModule;
|
||||
private PyObject interrupt;
|
||||
private boolean scriptMethodsInjected;
|
||||
private boolean cleanedUp;
|
||||
|
||||
/**
|
||||
* Gets a new GhidraPythonInterpreter instance.
|
||||
*
|
||||
* @return A new GhidraPythonInterpreter. Could be null if it failed to be created.
|
||||
*/
|
||||
public static GhidraJythonInterpreter get() {
|
||||
|
||||
// Initialize the python environment if necessary. Only needs to happen once.
|
||||
if (!pythonInitialized) {
|
||||
try {
|
||||
// Setup python home directory
|
||||
JythonUtils.setupJythonHomeDir();
|
||||
|
||||
// Setup python cache directory
|
||||
JythonUtils.setupJythonCacheDir(TaskMonitor.DUMMY);
|
||||
|
||||
// Indicate that we've initialized the python environment, which should
|
||||
// only happen once.
|
||||
pythonInitialized = true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(GhidraJythonInterpreter.class, null, "Python error",
|
||||
"Problem getting Ghirda Python interpreter", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up our default system state, including prompt styles.
|
||||
PySystemState state = new PySystemState();
|
||||
state.ps1 = new PyString(">>> ");
|
||||
state.ps2 = new PyString("... ");
|
||||
|
||||
// Return a new instance of our interpreter
|
||||
return new GhidraJythonInterpreter(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Ghidra python interpreter object.
|
||||
*
|
||||
* @param state The initial system state of the interpreter.
|
||||
*/
|
||||
private GhidraJythonInterpreter(PySystemState state) {
|
||||
super(null, state);
|
||||
|
||||
// Store the default python path in case we need to reset it later.
|
||||
defaultPythonPath = new ArrayList<>();
|
||||
for (Object object : systemState.path) {
|
||||
defaultPythonPath.add(Py.newStringOrUnicode(object.toString()));
|
||||
}
|
||||
|
||||
// Allow interruption of python code to occur when various code paths are
|
||||
// encountered.
|
||||
interruptTraceFunction = new InterruptTraceFunction();
|
||||
|
||||
// Setup __main__ module
|
||||
PyModule mod = imp.addModule("__main__");
|
||||
setLocals(mod.__dict__);
|
||||
|
||||
// Load site.py (standard Python practice).
|
||||
// This will also load our sitecustomize.py module.
|
||||
imp.load("site");
|
||||
|
||||
// Setup code completion module.
|
||||
// Note that this is not exported to the global address space by default.
|
||||
introspectModule = (PyModule) imp.load("jintrospect");
|
||||
|
||||
// Add __builtin__ module for code completion
|
||||
builtinModule = (PyModule) imp.load("__builtin__");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes/resets the python path to include all known Ghidra script paths.
|
||||
*/
|
||||
private void initializePythonPath() {
|
||||
|
||||
// Restore the python path back to default.
|
||||
systemState.path.retainAll(defaultPythonPath);
|
||||
|
||||
// Add in Ghidra script source directories
|
||||
for (ResourceFile resourceFile : GhidraScriptUtil.getEnabledScriptSourceDirectories()) {
|
||||
systemState.path.append(Py.newStringOrUnicode(resourceFile.getFile(false).getAbsolutePath()));
|
||||
}
|
||||
|
||||
for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedCompiledSourceBundlePaths()) {
|
||||
systemState.path.append(Py.newStringOrUnicode(resourceFile.getFile(false).getAbsolutePath()));
|
||||
}
|
||||
|
||||
// Add in the PyDev remote debugger module
|
||||
if (!SystemUtilities.isInDevelopmentMode()) {
|
||||
File pyDevSrcDir = PyDevUtils.getPyDevSrcDir();
|
||||
if (pyDevSrcDir != null) {
|
||||
systemState.path.append(Py.newStringOrUnicode(pyDevSrcDir.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes (executes) a line of Python to the interpreter.
|
||||
*
|
||||
* @param line the line of Python to push to the interpreter
|
||||
* @param script a PythonScript from which we load state (or null)
|
||||
* @return true if more input is needed before execution can occur
|
||||
* @throws PyException if an unhandled exception occurred while executing the line of python
|
||||
* @throws IllegalStateException if this interpreter has been cleaned up.
|
||||
*/
|
||||
public synchronized boolean push(String line, JythonScript script)
|
||||
throws PyException, IllegalStateException {
|
||||
|
||||
if (cleanedUp) {
|
||||
throw new IllegalStateException(
|
||||
"Ghidra python interpreter has already been cleaned up.");
|
||||
}
|
||||
|
||||
initializePythonPath();
|
||||
injectScriptHierarchy(script);
|
||||
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append("\n");
|
||||
}
|
||||
buffer.append(line);
|
||||
Py.getThreadState().tracefunc = interruptTraceFunction;
|
||||
Py.getSystemState().stderr = getSystemState().stderr; // needed to properly display SyntaxError
|
||||
boolean more;
|
||||
try {
|
||||
more = runsource(buffer.toString(), "python");
|
||||
getSystemState().stderr.invoke("flush");
|
||||
if (!more) {
|
||||
resetbuffer();
|
||||
}
|
||||
}
|
||||
catch (PyException pye) {
|
||||
resetbuffer();
|
||||
throw pye;
|
||||
}
|
||||
|
||||
return more;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a python file using this interpreter.
|
||||
*
|
||||
* @param file The python file to execute.
|
||||
* @param script A PythonScript from which we load state (or null).
|
||||
* @throws IllegalStateException if this interpreter has been cleaned up.
|
||||
*/
|
||||
public synchronized void execFile(ResourceFile file, JythonScript script)
|
||||
throws IllegalStateException {
|
||||
|
||||
if (cleanedUp) {
|
||||
throw new IllegalStateException(
|
||||
"Ghidra python interpreter has already been cleaned up.");
|
||||
}
|
||||
|
||||
initializePythonPath();
|
||||
injectScriptHierarchy(script);
|
||||
|
||||
Py.getThreadState().tracefunc = interruptTraceFunction;
|
||||
|
||||
// The Python import system sets the __file__ attribute to the file it's executing
|
||||
setVariable("__file__", new PyString(file.getAbsolutePath()));
|
||||
|
||||
// If the remote python debugger is alive, initialize it by calling settrace()
|
||||
if (!SystemUtilities.isInDevelopmentMode() && !SystemUtilities.isInHeadlessMode()) {
|
||||
if (PyDevUtils.getPyDevSrcDir() != null) {
|
||||
try {
|
||||
InetAddress localhost = InetAddress.getLocalHost();
|
||||
new Socket(localhost, PyDevUtils.PYDEV_REMOTE_DEBUGGER_PORT).close();
|
||||
Msg.info(this, "Python debugger found");
|
||||
StringBuilder dbgCmds = new StringBuilder();
|
||||
dbgCmds.append("import pydevd;");
|
||||
dbgCmds.append("pydevd.threadingCurrentThread().__pydevd_main_thread = True;");
|
||||
dbgCmds.append("pydevd.settrace(host=\"" + localhost.getHostName() +
|
||||
"\", port=" + PyDevUtils.PYDEV_REMOTE_DEBUGGER_PORT + ", suspend=False);");
|
||||
exec(dbgCmds.toString());
|
||||
Msg.info(this, "Connected to a python debugger.");
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.info(this, "Not connected to a python debugger.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run python file
|
||||
execfile(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cleanup() {
|
||||
super.cleanup();
|
||||
cleanedUp = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the given string to the interpreter's error stream with a newline
|
||||
* appended.
|
||||
*
|
||||
* @param str The string to print.
|
||||
*/
|
||||
void printErr(String str) {
|
||||
try {
|
||||
getSystemState().stderr.invoke("write", new PyString(str + "\n"));
|
||||
getSystemState().stderr.invoke("flush");
|
||||
}
|
||||
catch (PyException e) {
|
||||
// if the python interp state's stdin/stdout/stderr is messed up, it can throw an error
|
||||
Msg.error(this, "Failed to write to stderr", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interpreter's primary prompt.
|
||||
*
|
||||
* @return The interpreter's primary prompt.
|
||||
*/
|
||||
synchronized String getPrimaryPrompt() {
|
||||
return getSystemState().ps1.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interprester's secondary prompt.
|
||||
*
|
||||
* @return The interpreter's secondary prompt.
|
||||
*/
|
||||
synchronized String getSecondaryPrompt() {
|
||||
return getSystemState().ps2.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a KeyboardInterrupt.
|
||||
* <p>
|
||||
* This will attempt to interrupt the interpreter if it is running. There are
|
||||
* two types of things this interrupt will work on:
|
||||
* <p>
|
||||
* 1: A batched series of python commands (such as a loop). This works by setting
|
||||
* our interrupt flag that is checked by our {@link InterruptTraceFunction} when
|
||||
* various trace events happen.
|
||||
* <p>
|
||||
* 2: A sleeping or otherwise interruptible python command. Since jython is all
|
||||
* java under the hood, a sleep is really just a {@link Thread#sleep}, which we can
|
||||
* kick with a {@link Thread#interrupt()}.
|
||||
* <p>
|
||||
* If another type of thing is taking a really long time, this interrupt will fail.
|
||||
*
|
||||
* @param pythonThread The Python Thread we need to interrupt.
|
||||
*/
|
||||
void interrupt(Thread pythonThread) {
|
||||
final long INTERRUPT_TIMEOUT = 5000;
|
||||
|
||||
if ((pythonThread != null) && pythonThread.isAlive()) {
|
||||
|
||||
// Set trace interrupt flag
|
||||
interrupt = Py.KeyboardInterrupt;
|
||||
|
||||
// Wake potentially sleeping python command
|
||||
pythonThread.interrupt();
|
||||
|
||||
try {
|
||||
pythonThread.join(INTERRUPT_TIMEOUT);
|
||||
if (pythonThread.isAlive()) {
|
||||
printErr("Cannot interrupt running command");
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Nothing to do
|
||||
}
|
||||
interrupt = null;
|
||||
}
|
||||
else {
|
||||
printErr("KeyboardInterrupt");
|
||||
}
|
||||
resetbuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects all of the accessible fields and methods found in the PythonScript class hierarchy into
|
||||
* the given interpreter's Python address space.
|
||||
*
|
||||
* @param script The script whose class hierarchy is to be used for injection.
|
||||
*/
|
||||
void injectScriptHierarchy(JythonScript script) {
|
||||
|
||||
if (script == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject 'this'
|
||||
setVariable("this", script);
|
||||
|
||||
// Loop though the script class hierarchy
|
||||
for (Class<?> scriptClass = script.getClass(); scriptClass != Object.class; scriptClass =
|
||||
scriptClass.getSuperclass()) {
|
||||
|
||||
// Add public and protected fields
|
||||
for (Field field : scriptClass.getDeclaredFields()) {
|
||||
if (Modifier.isPublic(field.getModifiers()) ||
|
||||
Modifier.isProtected(field.getModifiers())) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
setVariable(field.getName(), field.get(script));
|
||||
}
|
||||
catch (IllegalAccessException iae) {
|
||||
throw new AssertException("Unexpected security manager being used!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add public methods (only once). Ignore inner classes.
|
||||
//
|
||||
// NOTE: We currently do not have a way to safely add protected methods. Disabling
|
||||
// python.security.respectJavaAccessibility and adding in protected methods in the below
|
||||
// loop caused an InaccessibleObjectException for some users (relating to core Java
|
||||
// modules, not the GhidraScript class hierarchy).
|
||||
if (!scriptMethodsInjected) {
|
||||
for (Method method : scriptClass.getDeclaredMethods()) {
|
||||
if (!method.getName().contains("$") &&
|
||||
Modifier.isPublic(method.getModifiers())) {
|
||||
method.setAccessible(true);
|
||||
setMethod(script, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scriptMethodsInjected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely sets a variable in the interpreter's namespace. This first checks to
|
||||
* make sure that we are not overriding a builtin Python symbol.
|
||||
*
|
||||
* @param varName The name of variable.
|
||||
* @param obj The value of the variable.
|
||||
* @return True if the variable was set; false if it already existed and wasn't set.
|
||||
*/
|
||||
private boolean setVariable(String varName, Object obj) {
|
||||
if (builtinModule.__findattr__(varName) == null) {
|
||||
set(varName, obj);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a bound (callback/function pointer) method as a local variable in the interpreter.
|
||||
*
|
||||
* @param obj A Java object that contains the method to bind.
|
||||
* @param method The method from the object to bind.
|
||||
* @return True if the method was set; false if it already existed and wasn't set.
|
||||
*/
|
||||
private boolean setMethod(Object obj, Method method) {
|
||||
String methodName = method.getName();
|
||||
|
||||
// First, check to make sure we're not shadowing any internal Python keywords/functions/etc
|
||||
if (builtinModule.__findattr__(methodName) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, we're safe to set it
|
||||
PyObject pyObj = get(methodName);
|
||||
if ((null == pyObj) || (pyObj instanceof PyNone)) {
|
||||
// This is the first method of this name that we are adding. Create a new bound PyMethod
|
||||
// to bind the Java method to the Java object in the Python world.
|
||||
set(methodName, new PyMethod(new PyReflectedFunction(method), Py.java2py(obj),
|
||||
Py.java2py(obj.getClass())));
|
||||
}
|
||||
else if (pyObj instanceof PyMethod) {
|
||||
// Another method of this name has already been added. Add it to the list of possibilities
|
||||
// (different arguments to methods on the same Object). But first, we must do some sanity
|
||||
// checks.
|
||||
PyMethod pyMethod = (PyMethod) pyObj;
|
||||
if ((pyMethod.__self__._is(Py.java2py(obj))) != Py.True) {
|
||||
Msg.error(this,
|
||||
"Method " + methodName + " of " + obj + " attempting to shadow method " +
|
||||
pyMethod.__func__ + " of " + pyMethod.__self__);
|
||||
return false;
|
||||
}
|
||||
if (!(pyMethod.__func__ instanceof PyReflectedFunction)) {
|
||||
Msg.error(this, "For addition of method " + methodName + " of " + obj +
|
||||
", cannot mix with non Java function " + pyMethod.__func__);
|
||||
return false;
|
||||
}
|
||||
((PyReflectedFunction) pyMethod.__func__).addMethod(method);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the possible command completions for a command.
|
||||
*
|
||||
* @param cmd The command line.
|
||||
* @param includeBuiltins True if we should include python built-ins; otherwise, false.
|
||||
* @param caretPos The position of the caret in the input string 'cmd'
|
||||
* @return A list of possible command completions. Could be empty if there aren't any.
|
||||
* @see JythonPlugin#getCompletions
|
||||
*/
|
||||
List<CodeCompletion> getCommandCompletions(String cmd, boolean includeBuiltins, int caretPos) {
|
||||
// At this point the caret is assumed to be positioned right after the value we need to
|
||||
// complete (example: "[complete.Me<caret>, rest, code]"). To make the completion work
|
||||
// in our case, it's sufficient (albeit naive) to just remove the text on the right side
|
||||
// of our caret. The later code (on the python's side) will parse the rest properly
|
||||
// and will generate the completions.
|
||||
cmd = cmd.substring(0, caretPos);
|
||||
|
||||
if ((cmd.length() > 0) && (cmd.charAt(cmd.length() - 1) == '(')) {
|
||||
return getMethodCommandCompletions(cmd);
|
||||
}
|
||||
return getPropertyCommandCompletions(cmd, includeBuiltins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns method documentation for the current command.
|
||||
*
|
||||
* @param cmd the current command
|
||||
* @return method documentation for the current command
|
||||
*/
|
||||
private List<CodeCompletion> getMethodCommandCompletions(String cmd) {
|
||||
List<CodeCompletion> completion_list = new ArrayList<>();
|
||||
try {
|
||||
PyObject getCallTipJava = introspectModule.__findattr__("getCallTipJava");
|
||||
PyString command = new PyString(cmd);
|
||||
PyObject locals = getLocals();
|
||||
|
||||
// Return value is (name, argspec, tip_text)
|
||||
ListIterator<?> iter =
|
||||
((List<?>) getCallTipJava.__call__(command, locals)).listIterator();
|
||||
while (iter.hasNext()) {
|
||||
String completion_portion = iter.next().toString();
|
||||
if (!completion_portion.equals("")) {
|
||||
String[] substrings = completion_portion.split("\n");
|
||||
for (String substring : substrings) {
|
||||
completion_list.add(new CodeCompletion(substring, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
}
|
||||
return completion_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of property->string_substitution pairs.
|
||||
*
|
||||
* @param cmd current command
|
||||
* @param includeBuiltins True if we should include python built-ins; otherwise, false.
|
||||
* @return A list of possible command completions. Could be empty if there aren't any.
|
||||
*/
|
||||
private List<CodeCompletion> getPropertyCommandCompletions(String cmd,
|
||||
boolean includeBuiltins) {
|
||||
try {
|
||||
PyObject getAutoCompleteList = introspectModule.__findattr__("getAutoCompleteList");
|
||||
PyString command = new PyString(cmd);
|
||||
PyStringMap locals = ((PyStringMap) getLocals()).copy();
|
||||
if (includeBuiltins) {
|
||||
// Add in the __builtin__ module's contents for the search
|
||||
locals.update(builtinModule.__dict__);
|
||||
}
|
||||
List<?> list = (List<?>) getAutoCompleteList.__call__(command, locals);
|
||||
return CollectionUtils.asList(list, CodeCompletion.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom trace function that allows interruption of python code to occur when various code
|
||||
* paths are encountered.
|
||||
*/
|
||||
class InterruptTraceFunction extends TraceFunction {
|
||||
private void checkInterrupt() {
|
||||
if (interrupt != null) {
|
||||
throw Py.makeException(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceCall(PyFrame frame) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceReturn(PyFrame frame, PyObject ret) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceLine(PyFrame frame, int line) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceFunction traceException(PyFrame frame, PyException exc) {
|
||||
checkInterrupt();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import org.python.core.PyInstance;
|
||||
import org.python.core.PyObject;
|
||||
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Generates CodeCompletions from Jython objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class JythonCodeCompletionFactory {
|
||||
private static List<Class<?>> classes = new ArrayList<>();
|
||||
private static Map<Class<?>, Color> classToColorMap = new HashMap<>();
|
||||
/* necessary because we only want to show the user the simple class name
|
||||
* Well, that, and the Options.DELIMITER is a '.' which totally messes
|
||||
* things up.
|
||||
*/
|
||||
private static Map<String, Class<?>> simpleNameToClass = new HashMap<>();
|
||||
private static Map<Class<?>, String> classDescription = new HashMap<>();
|
||||
public static final String COMPLETION_LABEL = "Code Completion Colors";
|
||||
|
||||
/* package-level accessibility so that JythonPlugin can tell this is
|
||||
* our option
|
||||
*/
|
||||
final static String INCLUDE_TYPES_LABEL = "Include type names in code completion popup?";
|
||||
private final static String INCLUDE_TYPES_DESCRIPTION =
|
||||
"Whether or not to include the type names (classes) of the possible " +
|
||||
"completions in the code completion window. The class name will be " +
|
||||
"parenthesized after the completion.";
|
||||
private final static boolean INCLUDE_TYPES_DEFAULT = true;
|
||||
private static boolean includeTypes = INCLUDE_TYPES_DEFAULT;
|
||||
|
||||
//@formatter:off
|
||||
public static final Color NULL_COLOR = new GColor("color.fg.plugin.jython.syntax.null");
|
||||
public static final Color FUNCTION_COLOR = new GColor("color.fg.plugin.jython.syntax.function");
|
||||
public static final Color PACKAGE_COLOR = new GColor("color.fg.plugin.jython.syntax.package");
|
||||
public static final Color CLASS_COLOR = new GColor("color.fg.plugin.jython.syntax.class");
|
||||
public static final Color METHOD_COLOR = new GColor("color.fg.plugin.jython.syntax.method");
|
||||
/* anonymous code chunks */
|
||||
public static final Color CODE_COLOR = new GColor("color.fg.plugin.jython.syntax.code");
|
||||
public static final Color INSTANCE_COLOR = new GColor("color.fg.plugin.jython.syntax.instance");
|
||||
public static final Color SEQUENCE_COLOR = new GColor("color.fg.plugin.jython.syntax.sequence");
|
||||
public static final Color MAP_COLOR = new GColor("color.fg.plugin.jython.syntax.map");
|
||||
public static final Color NUMBER_COLOR = new GColor("color.fg.plugin.jython.syntax.number");
|
||||
/* for weird Jython-specific stuff */
|
||||
public static final Color SPECIAL_COLOR = new GColor("color.fg.plugin.jython.syntax.special");
|
||||
//@formatter:on
|
||||
|
||||
static {
|
||||
/* Order matters! This is the order in which classes are checked for
|
||||
* coloring.
|
||||
*/
|
||||
setupClass("org.python.core.PyNone", NULL_COLOR, "'None' (null) Objects");
|
||||
|
||||
setupClass("org.python.core.PyReflectedFunction", FUNCTION_COLOR,
|
||||
"Python functions written in Java");
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.BuiltinFunctions", FUNCTION_COLOR,
|
||||
// "Python's built-in functions collection (note that many are " +
|
||||
// "re-implemented in Java)");
|
||||
setupClass("org.python.core.__builtin__", FUNCTION_COLOR,
|
||||
"Python's built-in functions collection (note that many are " +
|
||||
"re-implemented in Java)");
|
||||
setupClass("org.python.core.PyFunction", FUNCTION_COLOR, "functions written in Python");
|
||||
setupClass("org.python.core.PyMethodDescr", FUNCTION_COLOR,
|
||||
"unbound Python builtin instance methods (they take an " +
|
||||
"Object as the first argument)");
|
||||
|
||||
setupClass("org.python.core.PyJavaPackage", PACKAGE_COLOR, "Java packages");
|
||||
setupClass("org.python.core.PyModule", PACKAGE_COLOR, "Python modules");
|
||||
|
||||
/* Even though the latter is a subclass of the former, this allows
|
||||
* the user to differentiate visually Java classes from Python classes
|
||||
* if they so wish. But we don't do this by default.
|
||||
*/
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.PyJavaClass", CLASS_COLOR,
|
||||
// "Java classes");
|
||||
setupClass("org.python.core.PyJavaType", CLASS_COLOR, "Java classes");
|
||||
setupClass("org.python.core.PyClass", CLASS_COLOR, "Python classes");
|
||||
setupClass("org.python.core.PyType", CLASS_COLOR, "core Python types");
|
||||
|
||||
setupClass("org.python.core.PyMethod", METHOD_COLOR, "methods");
|
||||
setupClass("org.python.core.PyBuiltinFunction", METHOD_COLOR,
|
||||
"core Python methods, often inherited from Python's Object " +
|
||||
"(overriding these methods is very powerful)");
|
||||
|
||||
setupClass("org.python.core.PySequence", SEQUENCE_COLOR,
|
||||
"iterable sequences, including arrays, list, and strings");
|
||||
|
||||
setupClass("org.python.core.PyDictionary", MAP_COLOR, "arbitrary Python mapping type");
|
||||
setupClass("org.python.core.PyStringMap", MAP_COLOR, "Python String->Object mapping type");
|
||||
|
||||
setupClass("org.python.core.PyInteger", NUMBER_COLOR, "integers");
|
||||
setupClass("org.python.core.PyLong", NUMBER_COLOR, "long integers");
|
||||
setupClass("org.python.core.PyFloat", NUMBER_COLOR, "floating-point (decimal) numbers");
|
||||
setupClass("org.python.core.PyComplex", NUMBER_COLOR, "complex numbers");
|
||||
|
||||
setupClass("org.python.core.PyCompoundCallable", SPECIAL_COLOR,
|
||||
"special Python properties for " +
|
||||
"assigning Python functions as EventListeners on Java objects");
|
||||
|
||||
/* changed for Jython 2.5 */
|
||||
setupClass("org.python.core.PyObjectDerived", INSTANCE_COLOR, "Java Objects");
|
||||
setupClass("org.python.core.PyInstance", INSTANCE_COLOR, "Python Objects");
|
||||
|
||||
setupClass("org.python.core.PyCode", CODE_COLOR, "chunks of Python code");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param klass a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(Class<?> klass) {
|
||||
return getSimpleName(klass.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param className name of a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(String className) {
|
||||
/* lastIndexOf returns -1 on not found, so this works whether or not
|
||||
* a period is actually in className
|
||||
*/
|
||||
return className.substring(className.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a Class mapping.
|
||||
*
|
||||
* @param className Class name
|
||||
* @param defaultColor default Color for this Class
|
||||
* @param description description of the Class
|
||||
*/
|
||||
private static void setupClass(String className, Color defaultColor, String description) {
|
||||
try {
|
||||
Class<?> klass = Class.forName(className);
|
||||
classes.add(klass);
|
||||
classToColorMap.put(klass, defaultColor);
|
||||
simpleNameToClass.put(getSimpleName(klass), klass);
|
||||
classDescription.put(klass, description);
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
Msg.debug(JythonCodeCompletionFactory.class, "Unable to find class: " + className,
|
||||
cnfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CodeCompletion from the given Jython objects.
|
||||
*
|
||||
* @param description description of the new CodeCompletion
|
||||
* @param insertion what will be inserted to make the code complete
|
||||
* @param pyObj a Jython Object
|
||||
* @return A new CodeCompletion from the given Jython objects.
|
||||
* @deprecated use {@link #newCodeCompletion(String, String, PyObject, String)} instead,
|
||||
* it allows creation of substituting code completions
|
||||
*/
|
||||
@Deprecated
|
||||
public static CodeCompletion newCodeCompletion(String description, String insertion,
|
||||
PyObject pyObj) {
|
||||
return newCodeCompletion(description, insertion, pyObj, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CodeCompletion from the given Jython objects.
|
||||
*
|
||||
* @param description description of the new CodeCompletion
|
||||
* @param insertion what will be inserted to make the code complete
|
||||
* @param pyObj a Jython Object
|
||||
* @param userInput a word we want to complete, can be an empty string.
|
||||
* It's used to determine which part (if any) of the input should be
|
||||
* removed before the insertion of the completion
|
||||
* @return A new CodeCompletion from the given Jython objects.
|
||||
*/
|
||||
public static CodeCompletion newCodeCompletion(String description, String insertion,
|
||||
PyObject pyObj, String userInput) {
|
||||
JComponent comp = null;
|
||||
|
||||
if (pyObj != null) {
|
||||
if (includeTypes) {
|
||||
/* append the class name to the end of the description */
|
||||
String className = getSimpleName(pyObj.getClass());
|
||||
if (pyObj instanceof PyInstance) {
|
||||
/* get the real class */
|
||||
className = getSimpleName(((PyInstance) pyObj).instclass.__name__);
|
||||
}
|
||||
else if (className.startsWith("Py")) {
|
||||
/* strip off the "Py" */
|
||||
className = className.substring("Py".length());
|
||||
}
|
||||
description = description + " (" + className + ")";
|
||||
}
|
||||
|
||||
comp = new GDLabel(description);
|
||||
Iterator<Class<?>> iter = classes.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Class<?> testClass = iter.next();
|
||||
if (testClass.isInstance(pyObj)) {
|
||||
comp.setForeground(classToColorMap.get(testClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int charsToRemove = userInput.length();
|
||||
return new CodeCompletion(description, insertion, comp, charsToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Jython code completion Options.
|
||||
* @param plugin jython plugin as options owner
|
||||
* @param options an Options handle
|
||||
*/
|
||||
public static void setupOptions(JythonPlugin plugin, Options options) {
|
||||
includeTypes = options.getBoolean(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT);
|
||||
options.registerOption(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT, null,
|
||||
INCLUDE_TYPES_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an Option change.
|
||||
*
|
||||
* This is named slightly differently because it is a static method, not
|
||||
* an instance method.
|
||||
*
|
||||
* By the time we get here, we assume that the Option changed is indeed
|
||||
* ours.
|
||||
*
|
||||
* @param options the Options handle
|
||||
* @param name name of the Option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
public static void changeOptions(Options options, String name, Object oldValue,
|
||||
Object newValue) {
|
||||
if (name.equals(INCLUDE_TYPES_LABEL)) {
|
||||
includeTypes = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
else {
|
||||
Msg.error(JythonCodeCompletionFactory.class, "unknown option '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Java __call__ methods declared for a Jython object.
|
||||
*
|
||||
* Some Jython "methods" in the new-style Jython objects are actually
|
||||
* classes in and of themselves, re-implementing __call__ methods to
|
||||
* tell us how to call them. This returns an array of those Methods
|
||||
* (for code completion help).
|
||||
*
|
||||
* @param obj a PyObject
|
||||
* @return the Java __call__ methods declared for the Jython object
|
||||
*/
|
||||
public static Object[] getCallMethods(PyObject obj) {
|
||||
List<Method> callMethodList = new ArrayList<>();
|
||||
Method[] declaredMethods = obj.getClass().getDeclaredMethods();
|
||||
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
if (declaredMethod.getName().equals("__call__")) {
|
||||
callMethodList.add(declaredMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return callMethodList.toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.python.core.PySystemState;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
import generic.jar.ResourceFile;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.plugin.core.interpreter.*;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.task.*;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* This plugin provides the interactive Jython interpreter.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Jython Interpreter",
|
||||
description = "Provides an interactive Jython Interpreter that is tightly integrated with a loaded Ghidra program.",
|
||||
servicesRequired = { InterpreterPanelService.class },
|
||||
isSlowInstallation = true
|
||||
)
|
||||
//@formatter:on
|
||||
public class JythonPlugin extends ProgramPlugin
|
||||
implements InterpreterConnection, OptionsChangeListener {
|
||||
|
||||
private InterpreterConsole console;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
private JythonScript interactiveScript;
|
||||
private TaskMonitor interactiveTaskMonitor;
|
||||
private JythonPluginInputThread inputThread;
|
||||
|
||||
// Plugin options
|
||||
private final static String INCLUDE_BUILTINS_LABEL = "Include \"builtins\" in code completion?";
|
||||
private final static String INCLUDE_BUILTINS_DESCRIPTION =
|
||||
"Whether or not to include Jython's built-in functions and properties in the pop-up code completion window.";
|
||||
private final static boolean INCLUDE_BUILTINS_DEFAULT = true;
|
||||
|
||||
private static final Icon ICON = new GIcon("icon.plugin.jython");
|
||||
|
||||
private boolean includeBuiltins = INCLUDE_BUILTINS_DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JythonPlugin} object.
|
||||
*
|
||||
* @param tool The tool associated with this plugin.
|
||||
*/
|
||||
public JythonPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interpreter console.
|
||||
*
|
||||
* @return The plugin's interpreter console.
|
||||
*/
|
||||
InterpreterConsole getConsole() {
|
||||
return console;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's Jython interpreter.
|
||||
*
|
||||
* @return The plugin's Jython interpreter. May be null.
|
||||
*/
|
||||
GhidraJythonInterpreter getInterpreter() {
|
||||
return interpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive script
|
||||
*
|
||||
* @return The plugin's interactive script.
|
||||
*/
|
||||
JythonScript getInteractiveScript() {
|
||||
return interactiveScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive task monitor.
|
||||
*
|
||||
* @return The plugin's interactive task monitor.
|
||||
*/
|
||||
TaskMonitor getInteractiveTaskMonitor() {
|
||||
return interactiveTaskMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
console =
|
||||
getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false);
|
||||
welcome();
|
||||
console.addFirstActivationCallback(() -> resetInterpreter());
|
||||
createActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates various actions for the plugin.
|
||||
*/
|
||||
private void createActions() {
|
||||
|
||||
// Interrupt Interpreter
|
||||
DockingAction interruptAction = new DockingAction("Interrupt Interpreter", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
interrupt();
|
||||
}
|
||||
};
|
||||
interruptAction.setDescription("Interrupt Interpreter");
|
||||
interruptAction.setToolBarData(
|
||||
new ToolBarData(Icons.NOT_ALLOWED_ICON, null));
|
||||
interruptAction.setEnabled(true);
|
||||
interruptAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_I, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
|
||||
interruptAction.setHelpLocation(new HelpLocation(getTitle(), "Interrupt_Interpreter"));
|
||||
console.addAction(interruptAction);
|
||||
|
||||
// Reset Interpreter
|
||||
DockingAction resetAction = new DockingAction("Reset Interpreter", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
resetAction.setDescription("Reset Interpreter");
|
||||
resetAction.setToolBarData(
|
||||
new ToolBarData(Icons.REFRESH_ICON, null));
|
||||
resetAction.setEnabled(true);
|
||||
resetAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_D, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
|
||||
resetAction.setHelpLocation(new HelpLocation(getTitle(), "Reset_Interpreter"));
|
||||
console.addAction(resetAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the interpreter to a new starting state. This is used when the plugin is first
|
||||
* initialized, as well as when an existing interpreter receives a Jython exit command.
|
||||
* We used to try to reset the same interpreter, but it was really hard to do that correctly
|
||||
* so we now just create a brand new one.
|
||||
* <p>
|
||||
* NOTE: Loading Jython for the first time can be quite slow the first time, so we do this
|
||||
* when the user wants to first interact with the interpreter (rather than when the plugin loads).
|
||||
*/
|
||||
private void resetInterpreter() {
|
||||
|
||||
TaskLauncher.launchModal("Resetting Jython...", () -> {
|
||||
resetInterpreterInBackground();
|
||||
});
|
||||
}
|
||||
|
||||
// we expect this to be called from off the Swing thread
|
||||
private void resetInterpreterInBackground() {
|
||||
|
||||
// Reset the interpreter by creating a new one. Clean up the old one if present.
|
||||
if (interpreter == null) {
|
||||
|
||||
// Setup options
|
||||
ToolOptions options = tool.getOptions("Jython");
|
||||
includeBuiltins = options.getBoolean(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT);
|
||||
options.registerOption(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT, null,
|
||||
INCLUDE_BUILTINS_DESCRIPTION);
|
||||
options.addOptionsChangeListener(this);
|
||||
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
|
||||
// Setup code completion. This currently has to be done after the interpreter
|
||||
// is created. Otherwise an exception will occur.
|
||||
JythonCodeCompletionFactory.setupOptions(this, options);
|
||||
}
|
||||
else {
|
||||
inputThread.shutdown();
|
||||
inputThread = null;
|
||||
interpreter.cleanup();
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
}
|
||||
|
||||
// Reset the console.
|
||||
console.clear();
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
|
||||
// Tie the interpreter's input/output to the plugin's console.
|
||||
interpreter.setIn(console.getStdin());
|
||||
interpreter.setOut(console.getStdOut());
|
||||
interpreter.setErr(console.getStdErr());
|
||||
|
||||
// Print a welcome message.
|
||||
welcome();
|
||||
|
||||
// Setup the JythonScript describing the state of the interactive prompt.
|
||||
// This allows things like currentProgram and currentAddress to dynamically reflect
|
||||
// what's happening in the listing. Injecting the script hierarchy early here allows
|
||||
// code completion to work before commands are entered.
|
||||
interactiveScript = new JythonScript();
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), getCurrentProgram(), getProgramLocation(),
|
||||
getProgramSelection(), getProgramHighlight()),
|
||||
interactiveTaskMonitor, new PrintWriter(getConsole().getStdOut()));
|
||||
interpreter.injectScriptHierarchy(interactiveScript);
|
||||
interactiveTaskMonitor = new JythonInteractiveTaskMonitor(console.getStdOut());
|
||||
|
||||
// Start the input thread that receives jython commands to execute.
|
||||
inputThread = new JythonPluginInputThread(this);
|
||||
inputThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change in one of our options.
|
||||
*
|
||||
* @param options the options handle
|
||||
* @param optionName name of the option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
if (optionName.startsWith(JythonCodeCompletionFactory.COMPLETION_LABEL)) {
|
||||
JythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(JythonCodeCompletionFactory.INCLUDE_TYPES_LABEL)) {
|
||||
JythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(INCLUDE_BUILTINS_LABEL)) {
|
||||
includeBuiltins = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible command completion values.
|
||||
*
|
||||
* @param cmd current command line (without prompt)
|
||||
* @return A list of possible command completion values. Could be empty if there aren't any.
|
||||
*/
|
||||
@Override
|
||||
public List<CodeCompletion> getCompletions(String cmd) {
|
||||
return getCompletions(cmd, cmd.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible command completion values at the given position.
|
||||
*
|
||||
* @param cmd current command line (without prompt)
|
||||
* @param caretPos The position of the caret in the input string 'cmd'
|
||||
* @return A list of possible command completion values. Could be empty if there aren't any.
|
||||
*/
|
||||
@Override
|
||||
public List<CodeCompletion> getCompletions(String cmd, int caretPos) {
|
||||
// Refresh the environment
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), currentProgram, currentLocation,
|
||||
currentSelection, currentHighlight),
|
||||
interactiveTaskMonitor, console.getOutWriter());
|
||||
|
||||
return interpreter.getCommandCompletions(cmd, includeBuiltins, caretPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
// Terminate the input thread
|
||||
if (inputThread != null) {
|
||||
inputThread.shutdown();
|
||||
inputThread = null;
|
||||
}
|
||||
|
||||
// Dispose of the console
|
||||
if (console != null) {
|
||||
console.dispose();
|
||||
console = null;
|
||||
}
|
||||
|
||||
// Cleanup the interpreter
|
||||
if (interpreter != null) {
|
||||
interpreter.cleanup();
|
||||
interpreter = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupts what the interpreter is currently doing.
|
||||
*/
|
||||
public void interrupt() {
|
||||
if (interpreter == null) {
|
||||
return;
|
||||
}
|
||||
interpreter.interrupt(inputThread.getJythonPluginExecutionThread());
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the interpreter's state.
|
||||
*/
|
||||
public void reset() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
resetInterpreter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Jython";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPluginDescription().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a welcome message to the console.
|
||||
*/
|
||||
private void welcome() {
|
||||
console.getOutWriter().println("Jython Interpreter for Ghidra");
|
||||
console.getOutWriter().println("Based on Jython version " + PySystemState.version);
|
||||
console.getOutWriter().println("Press 'F1' for usage instructions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for cancelling execution using a TaskMonitor.
|
||||
*/
|
||||
class JythonInteractiveTaskMonitor extends TaskMonitorAdapter {
|
||||
private PrintWriter output = null;
|
||||
|
||||
public JythonInteractiveTaskMonitor(PrintWriter stdOut) {
|
||||
output = stdOut;
|
||||
}
|
||||
|
||||
public JythonInteractiveTaskMonitor(OutputStream stdout) {
|
||||
this(new PrintWriter(stdout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
output.println("<jython-interactive>: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a jython command for the plugin.
|
||||
*/
|
||||
class JythonPluginExecutionThread extends Thread {
|
||||
|
||||
private JythonPlugin plugin;
|
||||
private String cmd;
|
||||
private AtomicBoolean moreInputWanted;
|
||||
|
||||
/**
|
||||
* Creates a new jython plugin execution thread that executes the given command for the given
|
||||
* plugin.
|
||||
*
|
||||
* @param plugin The jython plugin to execute the command for.
|
||||
* @param cmd The jython command to execute.
|
||||
* @param moreInputWanted Gets set to indicate that the executed command expects more input.
|
||||
*/
|
||||
JythonPluginExecutionThread(JythonPlugin plugin, String cmd, AtomicBoolean moreInputWanted) {
|
||||
super("Jython plugin execution thread");
|
||||
|
||||
this.plugin = plugin;
|
||||
this.cmd = cmd;
|
||||
this.moreInputWanted = moreInputWanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
TaskMonitor interactiveTaskMonitor = plugin.getInteractiveTaskMonitor();
|
||||
JythonScript interactiveScript = plugin.getInteractiveScript();
|
||||
Program program = plugin.getCurrentProgram();
|
||||
|
||||
// Setup transaction for the execution.
|
||||
try (Transaction tx = program != null ? program.openTransaction("Jython command") : null) {
|
||||
// Setup Ghidra state to be passed into interpreter
|
||||
interactiveTaskMonitor.clearCancelled();
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
|
||||
PluginTool tool = plugin.getTool();
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), program, plugin.getProgramLocation(),
|
||||
plugin.getProgramSelection(), plugin.getProgramHighlight()),
|
||||
interactiveTaskMonitor, new PrintWriter(plugin.getConsole().getStdOut()));
|
||||
|
||||
// Execute the command
|
||||
moreInputWanted.set(false);
|
||||
moreInputWanted.set(plugin.getInterpreter().push(cmd, plugin.getInteractiveScript()));
|
||||
}
|
||||
catch (PyException pye) {
|
||||
String exceptionName = PyException.exceptionClassName(pye.type);
|
||||
if (exceptionName.equalsIgnoreCase("exceptions.SystemExit")) {
|
||||
plugin.reset();
|
||||
}
|
||||
else {
|
||||
plugin.getConsole()
|
||||
.getErrWriter()
|
||||
.println(
|
||||
"Suppressing exception: " + PyException.exceptionClassName(pye.type));
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
plugin.getConsole().getErrWriter().println("Stack overflow!");
|
||||
}
|
||||
finally {
|
||||
interactiveScript.end(false); // end any transactions the script may have started
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Thread responsible for getting interactive lines of jython from the plugin.
|
||||
* This class also kicks off the execution of that line in a new {@link JythonPluginExecutionThread}.
|
||||
*/
|
||||
class JythonPluginInputThread extends Thread {
|
||||
|
||||
private static int generationCount = 0;
|
||||
|
||||
private final JythonPlugin plugin;
|
||||
private final AtomicBoolean moreInputWanted = new AtomicBoolean(false);
|
||||
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
|
||||
private final InputStream consoleStdin;
|
||||
private JythonPluginExecutionThread jythonExecutionThread;
|
||||
|
||||
/**
|
||||
* Creates a new jython input thread that gets a line of jython input from the given plugin.
|
||||
*
|
||||
* @param plugin The jython plugin to get input from.
|
||||
*/
|
||||
JythonPluginInputThread(JythonPlugin plugin) {
|
||||
super("Jython plugin input thread (generation " + ++generationCount + ")");
|
||||
this.plugin = plugin;
|
||||
this.consoleStdin = plugin.getConsole().getStdin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last jython plugin execution thread that ran.
|
||||
*
|
||||
* @return The last jython plugin execution thread that ran. Could be null if one never ran.
|
||||
*/
|
||||
JythonPluginExecutionThread getJythonPluginExecutionThread() {
|
||||
return jythonExecutionThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(consoleStdin))) {
|
||||
String line;
|
||||
while (!shutdownRequested.get() && (line = reader.readLine()) != null) {
|
||||
|
||||
// Execute the line in a new thread
|
||||
jythonExecutionThread =
|
||||
new JythonPluginExecutionThread(plugin, line, moreInputWanted);
|
||||
jythonExecutionThread.start();
|
||||
|
||||
try {
|
||||
// Wait for the execution to finish
|
||||
jythonExecutionThread.join();
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// Hey we're back... a little earlier than expected, but there must be a reason.
|
||||
// So we'll go quietly.
|
||||
}
|
||||
|
||||
// Set the prompt appropriately
|
||||
plugin.getConsole()
|
||||
.setPrompt(
|
||||
moreInputWanted.get() ? plugin.getInterpreter().getSecondaryPrompt()
|
||||
: plugin.getInterpreter().getPrimaryPrompt());
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(JythonPluginInputThread.class,
|
||||
"Internal error reading commands from interpreter console. Please reset the interpreter.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the the background thread's run() loop to exit.
|
||||
* <p>
|
||||
* Causes background thread's exit by closing the inputstream it is looping on.
|
||||
*/
|
||||
void shutdown() {
|
||||
try {
|
||||
shutdownRequested.set(true);
|
||||
consoleStdin.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// shouldn't happen, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.python.util.jython;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Launcher entry point for running Ghidra from within Jython.
|
||||
*/
|
||||
public class JythonRun implements GhidraLaunchable {
|
||||
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) {
|
||||
|
||||
// Initialize the application
|
||||
ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration();
|
||||
Application.initializeApplication(layout, configuration);
|
||||
|
||||
// Setup jython home directory
|
||||
try {
|
||||
JythonUtils.setupJythonHomeDir();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JythonRun.class, null, "Jython home directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Setup jython cache directory
|
||||
try {
|
||||
JythonUtils.setupJythonCacheDir(configuration.getTaskMonitor());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JythonRun.class, null, "Jython cache directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.showError(JythonRun.class, null, "Operation cancelled", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Pass control to Jython
|
||||
jython.main(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* A Jython version of a {@link GhidraScript}.
|
||||
*/
|
||||
public class JythonScript extends GhidraScript {
|
||||
|
||||
static final String JYTHON_INTERPRETER = "ghidra.jython.interpreter";
|
||||
|
||||
private AtomicBoolean interpreterRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// Try to get the interpreter from an existing script state.
|
||||
GhidraJythonInterpreter interpreter =
|
||||
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
|
||||
|
||||
// Are we being called from an already running JythonScript with existing state?
|
||||
if (interpreter != null) {
|
||||
runInExistingEnvironment(interpreter);
|
||||
}
|
||||
else {
|
||||
runInNewEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runScript(String scriptName, GhidraState scriptState) throws Exception {
|
||||
GhidraJythonInterpreter interpreter =
|
||||
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
|
||||
if (interpreter == null) {
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
if (interpreter == null) {
|
||||
throw new AssertException("Could not get Ghidra Jython interpreter!");
|
||||
}
|
||||
}
|
||||
ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName);
|
||||
if (scriptSource != null) {
|
||||
GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource);
|
||||
GhidraScript ghidraScript = provider.getScriptInstance(scriptSource, writer);
|
||||
if (ghidraScript == null) {
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
updateStateFromVariables();
|
||||
}
|
||||
|
||||
if (ghidraScript instanceof JythonScript) {
|
||||
ghidraScript.set(scriptState, monitor, writer);
|
||||
JythonScript jythonScript = (JythonScript) ghidraScript;
|
||||
interpreter.execFile(jythonScript.getSourceFile(), jythonScript);
|
||||
}
|
||||
else {
|
||||
ghidraScript.execute(scriptState, monitor, writer);
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
loadVariablesFromState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in an existing interpreter environment.
|
||||
*
|
||||
* @param interpreter The existing interpreter to execute from.
|
||||
*/
|
||||
private void runInExistingEnvironment(GhidraJythonInterpreter interpreter) {
|
||||
interpreter.execFile(sourceFile, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in a new interpreter environment and sticks the new interpreter
|
||||
* in the script state so it can be retrieved by scripts called from this script.
|
||||
*/
|
||||
private void runInNewEnvironment() {
|
||||
|
||||
// Create new interpreter and stick it in the script's state.
|
||||
final GhidraJythonInterpreter interpreter = GhidraJythonInterpreter.get();
|
||||
final PrintWriter stdout = getStdOut();
|
||||
final PrintWriter stderr = getStdErr();
|
||||
interpreter.setOut(stdout);
|
||||
interpreter.setErr(stderr);
|
||||
|
||||
// We stick the interpreter in the state so that if the script calls runScript, that
|
||||
// script will use the same interpreter. It is questionable whether or not we should do
|
||||
// this (the new script will get all of the old script's variables), but changing it now
|
||||
// could break people's scripts if they expect this behavior.
|
||||
state.addEnvironmentVar(JYTHON_INTERPRETER, interpreter);
|
||||
|
||||
// Execute the script in a new thread.
|
||||
JythonScriptExecutionThread executionThread =
|
||||
new JythonScriptExecutionThread(this, interpreter, interpreterRunning);
|
||||
interpreterRunning.set(true);
|
||||
executionThread.start();
|
||||
|
||||
// Wait for the script be finish running
|
||||
while (interpreterRunning.get() && !monitor.isCancelled()) {
|
||||
Thread.yield();
|
||||
sleep100millis();
|
||||
}
|
||||
if (interpreterRunning.get()) {
|
||||
// We've been canceled. Interrupt the interpreter.
|
||||
interpreter.interrupt(executionThread);
|
||||
}
|
||||
|
||||
// Script is done. Make sure the output displays.
|
||||
stderr.flush();
|
||||
stdout.flush();
|
||||
|
||||
// Cleanup the interpreter, and remove it from the state (once it's cleaned it cannot be
|
||||
// reused)
|
||||
interpreter.cleanup();
|
||||
state.removeEnvironmentVar(JYTHON_INTERPRETER);
|
||||
}
|
||||
|
||||
private PrintWriter getStdOut() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdOut();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.out, true);
|
||||
}
|
||||
|
||||
private PrintWriter getStdErr() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdErr();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.err, true);
|
||||
}
|
||||
|
||||
private void sleep100millis() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Don't care; will probably be called again
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Jython";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a jython script from a file.
|
||||
*/
|
||||
class JythonScriptExecutionThread extends Thread {
|
||||
|
||||
private JythonScript script;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
private AtomicBoolean interpreterRunning;
|
||||
|
||||
/**
|
||||
* Creates a new jython script execution thread that executes the given jython script.
|
||||
*
|
||||
* @param script The jython script to execute.
|
||||
* @param interpreter The jython interpreter to use for execution.
|
||||
* @param interpreterRunning Gets set to indicate whether or not the interpreter is still running the script.
|
||||
*/
|
||||
JythonScriptExecutionThread(JythonScript script, GhidraJythonInterpreter interpreter,
|
||||
AtomicBoolean interpreterRunning) {
|
||||
super("Jython script execution thread");
|
||||
|
||||
this.script = script;
|
||||
this.interpreter = interpreter;
|
||||
this.interpreterRunning = interpreterRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
interpreter.execFile(script.getSourceFile(), script);
|
||||
}
|
||||
catch (PyException pye) {
|
||||
if (PyException.exceptionClassName(pye.type).equalsIgnoreCase(
|
||||
"exceptions.SystemExit")) {
|
||||
interpreter.printErr("SystemExit");
|
||||
}
|
||||
else {
|
||||
pye.printStackTrace(); // this prints to the interpreter error stream.
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
interpreter.printErr("Stack overflow!");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
interpreter.printErr(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
interpreterRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.*;
|
||||
|
||||
public class JythonScriptProvider extends GhidraScriptProvider {
|
||||
|
||||
private static final Pattern BLOCK_COMMENT = Pattern.compile("'''");
|
||||
|
||||
@Override
|
||||
public void createNewScript(ResourceFile newScript, String category) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)));
|
||||
writeHeader(writer, category);
|
||||
writer.println("");
|
||||
writeBody(writer);
|
||||
writer.println("");
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* In Jython this is a triple single quote sequence, "'''".
|
||||
*
|
||||
* @return the Pattern for Jython block comment openings
|
||||
*/
|
||||
@Override
|
||||
public Pattern getBlockCommentStart() {
|
||||
return BLOCK_COMMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* In Jython this is a triple single quote sequence, "'''".
|
||||
*
|
||||
* @return the Pattern for Jython block comment openings
|
||||
*/
|
||||
@Override
|
||||
public Pattern getBlockCommentEnd() {
|
||||
return BLOCK_COMMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentCharacter() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertifyHeaderStart() {
|
||||
return "## ###";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertificationBodyPrefix() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCertifyHeaderEnd() {
|
||||
return "##";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Jython";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return ".py";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
||||
throws GhidraScriptLoadException {
|
||||
|
||||
try {
|
||||
Class<?> clazz = Class.forName(JythonScript.class.getName());
|
||||
GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance();
|
||||
script.setSourceFile(sourceFile);
|
||||
return script;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new GhidraScriptLoadException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Python utility method class.
|
||||
*/
|
||||
public class JythonUtils {
|
||||
|
||||
public static final String JYTHON_NAME = "jython-2.7.3";
|
||||
public static final String JYTHON_CACHEDIR = "jython_cachedir";
|
||||
public static final String JYTHON_SRC = "jython-src";
|
||||
|
||||
/**
|
||||
* Sets up the jython home directory. This is the directory that has the "Lib" directory in it.
|
||||
*
|
||||
* @return The jython home directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the home directory.
|
||||
*/
|
||||
public static File setupJythonHomeDir() throws IOException {
|
||||
|
||||
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File jythonHomeDir =
|
||||
Application.getModuleDataSubDirectory(jythonModuleDir.getName(), JYTHON_NAME)
|
||||
.getFile(false);
|
||||
|
||||
if (!jythonHomeDir.exists()) {
|
||||
throw new IOException("Failed to find the jython home directory at: " + jythonHomeDir);
|
||||
}
|
||||
|
||||
System.setProperty("jython.home", jythonHomeDir.getAbsolutePath());
|
||||
|
||||
return jythonHomeDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the jython cache directory. This is a temporary space that jython source files
|
||||
* get compiled to and cached. It should NOT be in the Ghidra installation directory, because
|
||||
* some installations will not have the appropriate directory permissions to create new files in.
|
||||
*
|
||||
* @param monitor A monitor to use during the cache directory setup.
|
||||
* @return The jython cache directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the cache directory.
|
||||
* @throws CancelledException If the user cancelled the setup.
|
||||
*/
|
||||
public static File setupJythonCacheDir(TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
|
||||
File devDir = new File(Application.getUserSettingsDirectory(), "dev");
|
||||
File cacheDir = new File(devDir, JYTHON_CACHEDIR);
|
||||
if (!FileUtilities.mkdirs(cacheDir)) {
|
||||
throw new IOException("Failed to create the jython cache directory at: " + cacheDir);
|
||||
}
|
||||
|
||||
File jythonSrcDestDir = new File(cacheDir, JYTHON_SRC);
|
||||
if (!FileUtilities.createDir(jythonSrcDestDir)) {
|
||||
throw new IOException(
|
||||
"Failed to create the " + JYTHON_SRC + " directory at: " + jythonSrcDestDir);
|
||||
}
|
||||
|
||||
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File jythonSrcDir = new File(jythonModuleDir, JYTHON_SRC);
|
||||
if (!jythonSrcDir.exists()) {
|
||||
try {
|
||||
jythonSrcDir = Application.getModuleDataSubDirectory(jythonModuleDir.getName(),
|
||||
JYTHON_SRC).getFile(false);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new IOException("Failed to find the module's " + JYTHON_SRC + " directory");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
FileUtilities.copyDir(jythonSrcDir, jythonSrcDestDir, f -> f.getName().endsWith(".py"),
|
||||
monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException(
|
||||
"Failed to copy " + JYTHON_SRC + " files to: " + jythonSrcDestDir);
|
||||
}
|
||||
|
||||
System.setProperty("python.cachedir.skip", "false");
|
||||
System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
|
||||
System.setProperty("python.path", jythonSrcDestDir.getAbsolutePath());
|
||||
|
||||
return cacheDir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PyDevUtils {
|
||||
|
||||
public static final int PYDEV_REMOTE_DEBUGGER_PORT = 5678;
|
||||
|
||||
/**
|
||||
* Gets The PyDev source directory.
|
||||
*
|
||||
* @return The PyDev source directory, or null if it not known.
|
||||
*/
|
||||
public static File getPyDevSrcDir() {
|
||||
String property = System.getProperty("eclipse.pysrc.dir");
|
||||
return StringUtils.isNotBlank(property) ? new File(property) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent instantiation of utility class.
|
||||
*/
|
||||
private PyDevUtils() {
|
||||
}
|
||||
}
|
||||
BIN
Ghidra/Features/Jython/src/main/resources/images/python.png
Normal file
BIN
Ghidra/Features/Jython/src/main/resources/images/python.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -0,0 +1,173 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.*;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
/**
|
||||
* Tests for the Ghidra Python Interpreter's code completion functionality.
|
||||
*/
|
||||
public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private String simpleTestProgram = """
|
||||
my_int = 32
|
||||
my_bool = True
|
||||
my_string = 'this is a string'
|
||||
my_list = ["a", 2, 5.3, my_string]
|
||||
my_tuple = (1, 2, 3)
|
||||
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
|
||||
mY_None = None
|
||||
i = 5
|
||||
|
||||
def factorial(n):
|
||||
return 1 if n == 0 else n * factorial(n-1)
|
||||
def error_function():
|
||||
raise IOError("An IO error occurred!")
|
||||
|
||||
class Employee:
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
def getId(self):
|
||||
return self.id
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
employee = Employee(42, "Bob")
|
||||
""".stripIndent();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempScriptFolder = new TemporaryFolder();
|
||||
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
executeJythonProgram(simpleTestProgram);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
interpreter.cleanup();
|
||||
GhidraScriptUtil.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicCodeCompletion() {
|
||||
// test the "insertion" field
|
||||
// it should be equal to the full name of a variable we want to complete
|
||||
|
||||
List<String> completions = List.of("my_bool", "my_dictionary", "my_int", "my_list",
|
||||
"mY_None", "my_string", "my_tuple");
|
||||
assertCompletionsInclude("My", completions);
|
||||
assertCompletionsInclude("employee.Get", List.of("getId", "getName"));
|
||||
assertCompletionsInclude("('noise', (1 + fact", List.of("factorial"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharsToRemoveField() {
|
||||
// 'charsToRemove' field should be equal to the length of
|
||||
// a part of variable/function/method name we are trying to complete here.
|
||||
// This allows us to correctly put a completion in cases when we really
|
||||
// just want to replace a piece of text (i.e. "CURRENTAddress" => "currentAddress")
|
||||
// rather than simply 'complete' it.
|
||||
|
||||
assertCharsToRemoveEqualsTo("my_int", "my_int".length());
|
||||
assertCharsToRemoveEqualsTo("employee.get", "get".length());
|
||||
assertCharsToRemoveEqualsTo("('noise', (1 + fact", "fact".length());
|
||||
|
||||
assertCharsToRemoveEqualsTo("employee.", 0);
|
||||
assertCharsToRemoveEqualsTo("employee.getId(", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompletionsFromArbitraryCaretPositions() {
|
||||
// '<caret>' designates the position of the caret
|
||||
|
||||
String testCase;
|
||||
testCase = "MY_TUP<caret>";
|
||||
assertCompletionsInclude(testCase, List.of("my_tuple"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "my_tup".length());
|
||||
|
||||
testCase = "employee.Get<caret>; (4, 2+2)";
|
||||
assertCompletionsInclude(testCase, List.of("getId", "getName"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "Get".length());
|
||||
|
||||
testCase = "bar = e<caret> if 2+2==4 else 'baz'";
|
||||
assertCompletionsInclude(testCase, List.of("error_function", "employee"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "e".length());
|
||||
}
|
||||
|
||||
private void assertCompletionsInclude(String command, Collection<String> expectedCompletions) {
|
||||
Set<String> completions = getCodeCompletions(command)
|
||||
.stream()
|
||||
.map(c -> c.getInsertion())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var missing = new HashSet<String>(expectedCompletions);
|
||||
missing.removeAll(completions);
|
||||
if (!missing.isEmpty()) {
|
||||
Assert.fail("Could't find these completions: " + missing);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertCharsToRemoveEqualsTo(String command, int expectedCharsToRemove) {
|
||||
for (CodeCompletion comp : getCodeCompletions(command)) {
|
||||
assertEquals(String.format("%s; field 'charsToRemove' ", comp), expectedCharsToRemove,
|
||||
comp.getCharsToRemove());
|
||||
}
|
||||
}
|
||||
|
||||
private List<CodeCompletion> getCodeCompletions(String command) {
|
||||
// extract the caret position and generate completions relative to that place
|
||||
int caretPos = command.indexOf("<caret>");
|
||||
command.replace("<caret>", "");
|
||||
if (caretPos == -1) {
|
||||
caretPos = command.length();
|
||||
}
|
||||
|
||||
return interpreter.getCommandCompletions(command, false, caretPos);
|
||||
}
|
||||
|
||||
private void executeJythonProgram(String code) {
|
||||
try {
|
||||
File tempFile = tempScriptFolder.newFile();
|
||||
FileUtils.writeStringToFile(tempFile, code, Charset.defaultCharset());
|
||||
interpreter.execFile(new ResourceFile(tempFile), null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
/**
|
||||
* Tests the Ghidra python interpreter's functionality.
|
||||
*/
|
||||
public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private ByteArrayOutputStream out;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
out = new ByteArrayOutputStream();
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
interpreter.setOut(out);
|
||||
interpreter.setErr(out);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
out.reset();
|
||||
interpreter.cleanup();
|
||||
GhidraScriptUtil.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "push" method is working by executing a simple line of python.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonPush() {
|
||||
final String str = "hi";
|
||||
interpreter.push("print \"" + str + "\"", null);
|
||||
assertEquals(out.toString().trim(), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "execFile" method is working by executing a simple file of python.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonExecFile() {
|
||||
interpreter.execFile(new ResourceFile("ghidra_scripts/python_basics.py"), null);
|
||||
assertTrue(out.toString().contains("Snoopy"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that our sitecustomize.py modules gets loaded by testing the custom help function
|
||||
* that we install from there.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonSiteCustomize() {
|
||||
interpreter.push("help", null);
|
||||
assertTrue(out.toString().contains("Press 'F1'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that cleaning the interpreter invalidates it.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonCleanupInvalidation() {
|
||||
interpreter.cleanup();
|
||||
|
||||
try {
|
||||
interpreter.push("pass", null);
|
||||
fail("Push still worked after interpreter cleanup.");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// If everything worked, we should end up here.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
|
||||
/**
|
||||
* Tests the Python Plugin functionality.
|
||||
*/
|
||||
public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private JythonPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
tool.addPlugin(JythonPlugin.class.getName());
|
||||
plugin = env.getPlugin(JythonPlugin.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.dispose();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that issuing a reset from the plugin resets the interpreter.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonPluginReset() {
|
||||
GhidraJythonInterpreter origInterpreter = plugin.getInterpreter();
|
||||
plugin.reset();
|
||||
GhidraJythonInterpreter newInterpreter = plugin.getInterpreter();
|
||||
assertNotSame(origInterpreter, newInterpreter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.ScriptInfo;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
if (Files.notExists(userScriptDir)) {
|
||||
Files.createDirectories(userScriptDir);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetailedJythonScript() {
|
||||
String descLine1 = "This script exists to check that the info on";
|
||||
String descLine2 = "a script that has extensive documentation is";
|
||||
String descLine3 = "properly parsed and represented.";
|
||||
String author = "Fake Name";
|
||||
String categoryTop = "Test";
|
||||
String categoryBottom = "ScriptInfo";
|
||||
String keybinding = "ctrl shift COMMA";
|
||||
String menupath = "File.Run.Detailed Script";
|
||||
String importPackage = "detailStuff";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + descLine1,
|
||||
"#" + descLine2,
|
||||
"#" + descLine3,
|
||||
"#@author " + author,
|
||||
"#@category " + categoryTop + "." + categoryBottom,
|
||||
"#@keybinding " + keybinding,
|
||||
"#@menupath " + menupath,
|
||||
"#@importpackage " + importPackage,
|
||||
"print('for a blank class, it sure is well documented!')");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
|
||||
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
|
||||
assertEquals(expectedDescription, info.getDescription());
|
||||
|
||||
assertEquals(author, info.getAuthor());
|
||||
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
|
||||
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
|
||||
assertEquals(importPackage, info.getImportPackage());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(2, actualCategory.length);
|
||||
assertEquals(categoryTop, actualCategory[0]);
|
||||
assertEquals(categoryBottom, actualCategory[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithBlockCommentAndCertifyHeader() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"## ###",
|
||||
"# IP: GHIDRA",
|
||||
"# ",
|
||||
"# Some license text...",
|
||||
"# you may not use this file except in compliance with the License.",
|
||||
"# ",
|
||||
"# blah blah blah",
|
||||
"##",
|
||||
"",
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithoutBlockComment() {
|
||||
String description = "Script without a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithSingleLineBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''This is a test block comment. It will be ignored.'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException {
|
||||
File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir);
|
||||
tempFile.deleteOnExit();
|
||||
ResourceFile tempResourceFile = new ResourceFile(tempFile);
|
||||
|
||||
PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream());
|
||||
for (String line : lines) {
|
||||
writer.println(line);
|
||||
}
|
||||
writer.close();
|
||||
|
||||
return tempResourceFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Tests the Python script functionality.
|
||||
*/
|
||||
public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private ConsoleService console;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||
tool.addPlugin(ConsolePlugin.class.getName());
|
||||
console = tool.getService(ConsoleService.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.dispose();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Jython scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonScript() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
String output = runPythonScript(Application.getModuleFile("Jython", script));
|
||||
assertTrue(output.contains("Snoopy"));
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find jython script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Jython scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonInterpreterGoneFromState() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
runPythonScript(Application.getModuleFile("Jython", script), state);
|
||||
assertTrue(state.getEnvironmentVar(JythonScript.JYTHON_INTERPRETER) == null);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find python script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile) throws Exception {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
return runPythonScript(scriptFile, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script with the given initial state.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @param state The initial state of the script.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile, GhidraState state) throws Exception {
|
||||
|
||||
runSwing(() -> console.clearMessages());
|
||||
|
||||
JythonScriptProvider scriptProvider = new JythonScriptProvider();
|
||||
PrintWriter writer = new PrintWriter(new ByteArrayOutputStream());
|
||||
JythonScript script = (JythonScript) scriptProvider.getScriptInstance(scriptFile, writer);
|
||||
script.set(state, TaskMonitor.DUMMY, writer);
|
||||
script.run();
|
||||
|
||||
waitForSwing();
|
||||
|
||||
AtomicReference<String> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
String text = console.getText(0, console.getTextLength());
|
||||
ref.set(text);
|
||||
});
|
||||
|
||||
String text = ref.get();
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user