mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-28 10:27:35 +08:00
Merge remote-tracking branch 'origin/Ghidra_12.1'
This commit is contained in:
+3
-3
@@ -26,7 +26,7 @@
|
||||
script-specific code.</LI>
|
||||
|
||||
<LI>Of course if you choose Java, the Ghidra script must be written in Java.
|
||||
An implementation for Python (based on Jython) is also provided.</LI>
|
||||
An implementation for Python (based on PyGhidra) is also provided.</LI>
|
||||
</OL>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/New_Script_Editor.png" ></P>
|
||||
@@ -161,8 +161,8 @@
|
||||
uses the same script file extension. If left unspecified, the first Ghidra script runtime
|
||||
environment that matches the script's extension will be used.<BR>
|
||||
<BR>
|
||||
For example, specify <TT>"@runtime Jython"</TT> if the script is targetted for a Jython 2
|
||||
runtime environment rather than a Python 3 runtime environment.
|
||||
For example, specify <TT>"@runtime Jython"</TT> if the script is targeted for a Jython 2
|
||||
runtime environment rather than a CPython 3 (PyGhidra) runtime environment.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
DATA SEARCH IGNORE DIR: jython-2.7.4
|
||||
EXCLUDE FROM GHIDRA JAR: true
|
||||
MODULE FILE LICENSE: lib/jython-standalone-2.7.4.jar Jython License
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Jython
|
||||
@@ -1,67 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javadoc.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
eclipse.project.name = 'Features Jython'
|
||||
|
||||
|
||||
def JYTHON = "org.python:jython-standalone:2.7.4"
|
||||
def JYTHON_DIR = "jython-2.7.4"
|
||||
|
||||
configurations {
|
||||
jython
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':Base')
|
||||
jython JYTHON
|
||||
api JYTHON
|
||||
}
|
||||
|
||||
task jythonUnpack(type: Copy) {
|
||||
description = "Unpack Jython Lib"
|
||||
|
||||
// Without this, the copyTask will unzip the file to check for "up to date"
|
||||
onlyIf {
|
||||
!file("build/data/${JYTHON_DIR}").exists()
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady {
|
||||
from zipTree(configurations.jython.singleFile)
|
||||
include "Lib/**"
|
||||
|
||||
destinationDir = file("build/data/${JYTHON_DIR}")
|
||||
}
|
||||
}
|
||||
|
||||
task jythonSrcCopy(type: Copy) {
|
||||
description = "Copy jython-src directory (for Feature Jython)"
|
||||
|
||||
from(file("jython-src"))
|
||||
|
||||
destinationDir = file("build/data/jython-src")
|
||||
}
|
||||
|
||||
// Ensure that Jython is usable in development xx
|
||||
rootProject.prepDev.dependsOn jythonUnpack
|
||||
jar.dependsOn jythonUnpack
|
||||
jar.dependsOn jythonSrcCopy
|
||||
@@ -1,8 +0,0 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Jython License
|
||||
##MODULE IP: LGPL 2.1
|
||||
Module.manifest||GHIDRA||||END|
|
||||
README.md||GHIDRA||||END|
|
||||
data/jython.theme.properties||GHIDRA||||END|
|
||||
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
|
||||
src/main/help/help/topics/Jython/interpreter.html||GHIDRA||||END|
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
[Defaults]
|
||||
|
||||
color.fg.plugin.jython.syntax.class = color.palette.blue
|
||||
color.fg.plugin.jython.syntax.code = color.palette.darkgreen
|
||||
color.fg.plugin.jython.syntax.function = color.palette.green
|
||||
color.fg.plugin.jython.syntax.instance = color.palette.purple
|
||||
color.fg.plugin.jython.syntax.map = color.palette.steelblue
|
||||
color.fg.plugin.jython.syntax.method = color.palette.teal
|
||||
color.fg.plugin.jython.syntax.null = color.palette.red
|
||||
color.fg.plugin.jython.syntax.number = color.palette.darkgray
|
||||
color.fg.plugin.jython.syntax.package = color.palette.darkred
|
||||
color.fg.plugin.jython.syntax.sequence = color.palette.saddlebrown
|
||||
color.fg.plugin.jython.syntax.special = color.palette.darkgreen
|
||||
|
||||
icon.plugin.jython = python.png
|
||||
|
||||
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Adds a comment to a program.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
|
||||
from ghidra.program.model.address.Address import *
|
||||
from ghidra.program.model.listing.CodeUnit import *
|
||||
from ghidra.program.model.listing.Listing import *
|
||||
|
||||
minAddress = currentProgram.getMinAddress()
|
||||
listing = currentProgram.getListing()
|
||||
codeUnit = listing.getCodeUnitAt(minAddress)
|
||||
codeUnit.setComment(codeUnit.PLATE_COMMENT, "AddCommentToProgramScript - This is an added comment!")
|
||||
@@ -1,113 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example of asking for user input.
|
||||
|
||||
# Note the ability to pre-populate values for some of these variables when AskScript.properties file exists.
|
||||
# Also notice how the previous input is saved.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from ghidra.framework.model import DomainFile
|
||||
from ghidra.framework.model import DomainFolder
|
||||
from ghidra.program.model.address import Address
|
||||
from ghidra.program.model.lang import LanguageCompilerSpecPair
|
||||
from ghidra.program.model.listing import Program
|
||||
from ghidra.util import Msg
|
||||
|
||||
from java.lang import IllegalArgumentException
|
||||
from ghidra.util.exception import CancelledException
|
||||
|
||||
# The presence of the AskScript.properties file in the same location (as AskScript.java)
|
||||
# allows for the following behavior:
|
||||
# - GUI: if applicable, auto-populates the input field with the value in the
|
||||
# .properties file (the first time that input field appears)
|
||||
# - Headless: uses the value in the .properties file for the variable assigned to the
|
||||
# corresponding askXxx() method in the GhidraScript.
|
||||
try:
|
||||
file1 = askFile("FILE", "Choose file:")
|
||||
print "file was: " + str(file1)
|
||||
|
||||
directory1 = askDirectory("Directory", "Choose directory:")
|
||||
print "directory was: " + str(directory1)
|
||||
|
||||
lang = askLanguage("Language Picker", "I want this one!")
|
||||
print "language was: " + lang.toString()
|
||||
|
||||
domFolder = askProjectFolder("Please pick a domain folder!")
|
||||
print "domFolder was: " + domFolder.getName()
|
||||
|
||||
int1 = askInt("integer 1", "enter integer 1")
|
||||
int2 = askInt("integer 2", "enter integer 2")
|
||||
print "int1 + int2 = " + str(int1 + int2)
|
||||
|
||||
long1 = askLong("long 1", "enter long 1")
|
||||
long2 = askLong("long 2", "enter long 2")
|
||||
print "long1 + long2 = " + str(long1 + long2)
|
||||
|
||||
address1 = askAddress("address 1", "enter address 1")
|
||||
address2 = askAddress("address 2", "enter address 2")
|
||||
print "address1 + address2 = " + address1.add(address2.getOffset()).toString()
|
||||
|
||||
#bytes = askBytes("bytes", "enter byte pattern")
|
||||
#for b in bytes:
|
||||
# print "b = " + str(b & 0xff)
|
||||
|
||||
try:
|
||||
prog = askProgram("Please choose a program to open.")
|
||||
print "Program picked: " + prog.getName()
|
||||
finally:
|
||||
if prog is not None:
|
||||
prog.release(this)
|
||||
|
||||
domFile = askDomainFile("Which domain file would you like?")
|
||||
print "Domain file: " + domFile.getName()
|
||||
|
||||
d1 = askDouble("double 1", "enter double 1")
|
||||
d2 = askDouble("double 2", "enter double 2")
|
||||
print "d1 + d2 = " + str(d1 + d2)
|
||||
|
||||
myStr = askString("String Specification", "Please type a string: ")
|
||||
myOtherStr = askString("Another String Specification", "Please type another string: ", "replace me!")
|
||||
print "You typed: " + myStr + " and " + myOtherStr
|
||||
|
||||
choice = askChoice("Choice", "Please choose one", [ "grumpy", "dopey", "sleepy", "doc", "bashful" ], "bashful")
|
||||
print "Choice? " + choice
|
||||
|
||||
choices1 = askChoices("Choices 1", "Please choose one or more numbers.", [ 1, 2, 3, 4, 5, 6 ])
|
||||
print "Choices 1: "
|
||||
for intChoice in choices1:
|
||||
print str(intChoice) + " "
|
||||
print ""
|
||||
|
||||
choices2 = askChoices("Choices 2", "Please choose one or more of the following.",
|
||||
[ 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 ], ["Part 1", "Part 2", "Part 3", "Part 4", "Part 5", "Part 6" ])
|
||||
print "Choices 2: "
|
||||
for intChoice in choices2:
|
||||
print str(intChoice) + " "
|
||||
print ""
|
||||
|
||||
yesOrNo = askYesNo("yes or no", "is this a yes/no question?")
|
||||
print "Yes or No? " + str(yesOrNo)
|
||||
|
||||
except IllegalArgumentException as error:
|
||||
Msg.warn(self, "Error during headless processing: " + error.toString())
|
||||
except CancelledException:
|
||||
print "Cancelled"
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Shows how to run a script on all of the programs within the current project.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
# NOTE: Script will only process unversioned and checked-out files.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from ghidra.app.script import GhidraState
|
||||
from ghidra.framework.model import *
|
||||
from ghidra.program.database import ProgramContentHandler
|
||||
from ghidra.program.model.listing import Program
|
||||
from ghidra.util.exception import CancelledException
|
||||
from ghidra.util.exception import VersionException
|
||||
|
||||
from java.io import IOException
|
||||
|
||||
# The script referenced in the following line should be replaced with the script to be called
|
||||
SUBSCRIPT_NAME = "AddCommentToProgramScript.java"
|
||||
|
||||
def recurseProjectFolder(domainFolder):
|
||||
files = domainFolder.getFiles()
|
||||
for domainFile in files:
|
||||
processDomainFile(domainFile)
|
||||
folders = domainFolder.getFolders()
|
||||
for folder in folders:
|
||||
recurseProjectFolder(folder)
|
||||
|
||||
def processDomainFile(domainFile):
|
||||
if not ProgramContentHandler.PROGRAM_CONTENT_TYPE == domainFile.getContentType():
|
||||
return # skip non-Program files
|
||||
if domainFile.isVersioned() and not domainFile.isCheckedOut():
|
||||
println("WARNING! Skipping versioned file - not checked-out: " + domainFile.getPathname())
|
||||
program = None
|
||||
consumer = java.lang.Object()
|
||||
try:
|
||||
program = domainFile.getDomainObject(consumer, True, False, monitor)
|
||||
processProgram(program)
|
||||
|
||||
except VersionException:
|
||||
println("ERROR! Failed to process file due to upgrade issue: " + domainFile.getPathname())
|
||||
|
||||
finally:
|
||||
if program is not None:
|
||||
program.release(consumer)
|
||||
|
||||
def processProgram(program):
|
||||
"""Do you program work here """
|
||||
println("Processing: " + program.getDomainFile().getPathname())
|
||||
monitor.setMessage("Processing: " + program.getDomainFile().getName())
|
||||
id = program.startTransaction("Batch Script Transaction")
|
||||
try:
|
||||
newState = GhidraState(state.getTool(), state.getProject(), program, None, None, None)
|
||||
runScript(SUBSCRIPT_NAME, newState)
|
||||
|
||||
except Exception:
|
||||
printerr("ERROR! Exception occurred while processing file: " + program.getDomainFile().getPathname())
|
||||
printerr(" " + Exception.getMessage())
|
||||
e.printStackTrace()
|
||||
|
||||
finally:
|
||||
program.endTransaction(id, True)
|
||||
|
||||
# ...save any changes
|
||||
program.save("Changes made by script: " + SUBSCRIPT_NAME, monitor)
|
||||
|
||||
if currentProgram is not None:
|
||||
popup("This script should be run from a tool with no open programs")
|
||||
exit()
|
||||
|
||||
project = state.getProject()
|
||||
projectData = project.getProjectData()
|
||||
rootFolder = projectData.getRootFolder()
|
||||
recurseProjectFolder(rootFolder)
|
||||
@@ -1,25 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of a script calling another script.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
runScript("HelloWorldScript.java")
|
||||
runScript("HelloWorldPopupScript.java")
|
||||
@@ -1,37 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of a script prompting the user for a data type.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.framework.plugintool import PluginTool
|
||||
from ghidra.program.model.data import DataType
|
||||
from ghidra.program.model.data import DataTypeManager
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
|
||||
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType is not None:
|
||||
print "Chosen data type: " + str(dataType)
|
||||
@@ -1,49 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example of how to color the listing background
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from ghidra.app.plugin.core.colorizer import ColorizingService
|
||||
from ghidra.app.script import GhidraScript
|
||||
from ghidra.program.model.address import Address
|
||||
from ghidra.program.model.address import AddressSet
|
||||
|
||||
from java.awt import Color
|
||||
|
||||
service = state.getTool().getService(ColorizingService)
|
||||
if service is None:
|
||||
print "Can't find ColorizingService service"
|
||||
if currentSelection is not None:
|
||||
service.setBackgroundColor(currentSelection, Color(255, 200, 200))
|
||||
elif currentAddress is not None:
|
||||
service.setBackgroundColor(currentAddress, currentAddress, Color(255, 200, 200))
|
||||
else:
|
||||
print "No selection or current address to color"
|
||||
|
||||
anotherAddress = currentAddress.add(10)
|
||||
setBackgroundColor(anotherAddress, Color.YELLOW)
|
||||
|
||||
# create an address set with values you want to change
|
||||
addresses = AddressSet()
|
||||
addresses.add(currentAddress.add(10))
|
||||
addresses.add(currentAddress.add(11))
|
||||
addresses.add(currentAddress.add(12))
|
||||
setBackgroundColor(addresses, Color(100, 100, 200))
|
||||
@@ -1,44 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# An example using the Python string formatting.
|
||||
# See FormatExampleScript.java for examples of using the printf() method.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from time import *
|
||||
import java.util.Calendar
|
||||
|
||||
print "The %s jumped over the %s" % ("cow", "moon")
|
||||
|
||||
print "The %s jumped over the %s " % ("cow", "moon") + strftime("%X")
|
||||
|
||||
print "The %s jumped over the %s - timestamp: %s" % ("cow", "moon", strftime("%c %Z"))
|
||||
|
||||
print "The %s jumped over the %s at %s on %s" % ("cow", "moon", strftime("%I:%M%p"), strftime("%A, %b %d"))
|
||||
|
||||
print "Padding: %03d" % (1)
|
||||
|
||||
print "Hex: 0x%x" % (10)
|
||||
|
||||
print "Left-justified: %-10d" % (1)
|
||||
print "Right-justified: %10d" % (1)
|
||||
|
||||
print "String fill: '%10s'" % ("Fill")
|
||||
print "String fill, left justified: '%-10s'" % ("Fill")
|
||||
@@ -1,62 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Imports a text file containing symbol definitions, with a maximum of one symbol defined per line. Each symbol definition is in the form of "symbol_name address function_or_label", where "symbol_name" is the name of the symbol, "address" is the address of the symbol in one of the forms listed below, and "function_or_label" is either "f" or "l", with "f" indicating that a function is to be created and "l" indicating that a label is to be created.
|
||||
# Address formats are the same as those that can be used with the "Go to address" function. For example:
|
||||
# - 1234abcd
|
||||
# - 0x1234abcd
|
||||
# - ADDRESS_SPACE:1234abcd
|
||||
# - ADDRESS_SPACE:0x1234abcd
|
||||
# - MEMORY_REGION:1234abcd
|
||||
# - MEMORY_REGION:0x1234abcd
|
||||
# Omitting the address space or memory region specifier from the address will result in the function or label being created in the default address space.
|
||||
# @author unkown; edited by matedealer <git@matedealer.de>
|
||||
# @category Import
|
||||
# @runtime Jython
|
||||
#
|
||||
|
||||
from ghidra.program.model.symbol.SourceType import *
|
||||
import string
|
||||
|
||||
functionManager = currentProgram.getFunctionManager()
|
||||
|
||||
f = askFile("Give me a file to open", "Go baby go!")
|
||||
|
||||
for line in file(f.absolutePath): # note, cannot use open(), since that is in GhidraScript
|
||||
pieces = line.split()
|
||||
|
||||
name = pieces[0]
|
||||
address = toAddr(pieces[1])
|
||||
|
||||
try:
|
||||
function_or_label = pieces[2]
|
||||
except IndexError:
|
||||
function_or_label = "l"
|
||||
|
||||
|
||||
if function_or_label == "f":
|
||||
func = functionManager.getFunctionAt(address)
|
||||
|
||||
if func is not None:
|
||||
old_name = func.getName()
|
||||
func.setName(name, USER_DEFINED)
|
||||
print("Renamed function {} to {} at address {}".format(old_name, name, address))
|
||||
else:
|
||||
func = createFunction(address, name)
|
||||
print("Created function {} at address {}".format(name, address))
|
||||
|
||||
else:
|
||||
print("Created label {} at address {}".format(name, address))
|
||||
createLabel(address, name, False)
|
||||
@@ -1,21 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Prints out all the functions in the program that have a non-zero stack purge size
|
||||
# @runtime Jython
|
||||
|
||||
for func in currentProgram.getFunctionManager().getFunctions(currentProgram.evaluateAddress("0"), 1):
|
||||
if func.getStackPurgeSize() != 0:
|
||||
print "Function", func, "at", func.getEntryPoint(), "has nonzero purge size", func.getStackPurgeSize()
|
||||
@@ -1,37 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Writes properties to the tool.
|
||||
|
||||
# DISCLAIMER: This is a recreation of a Java Ghidra script for example
|
||||
# use only. Please run the Java version in a production environment.
|
||||
|
||||
#@category Examples.Python
|
||||
#@runtime Jython
|
||||
|
||||
from ghidra.framework.options import Options
|
||||
from ghidra.framework.plugintool import PluginTool
|
||||
|
||||
tool = state.getTool()
|
||||
options = tool.getOptions("name of my script")
|
||||
|
||||
fooString = options.getString("foo", None)
|
||||
|
||||
if fooString is not None : #does not exist in tool options
|
||||
fooString = askString("enter foo", "what value for foo:")
|
||||
if fooString is not None :
|
||||
options.setString("foo", fooString)
|
||||
|
||||
popup(fooString)
|
||||
@@ -1,34 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of being imported by a Ghidra Python script/module
|
||||
# @category: Examples.Python
|
||||
# @runtime Jython
|
||||
|
||||
# The following line will fail if this module is imported from external_module_caller.py,
|
||||
# because only the script that gets directly launched by Ghidra inherits fields and methods
|
||||
# from the GhidraScript/FlatProgramAPI.
|
||||
try:
|
||||
print currentProgram.getName()
|
||||
except NameError:
|
||||
print "Failed to get the program name"
|
||||
|
||||
# The Python module that Ghidra directly launches is always called __main__. If we import
|
||||
# everything from that module, this module will behave as if Ghidra directly launched it.
|
||||
from __main__ import *
|
||||
|
||||
# The below method call should now work
|
||||
print currentProgram.getName()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Example of importing an external Ghidra Python module
|
||||
# @category: Examples.Python
|
||||
# @runtime Jython
|
||||
|
||||
# Import the external module that wants to access the Ghidra scripting API.
|
||||
# NOTE: see external_module_callee.py for additional tips.
|
||||
import external_module_callee
|
||||
@@ -1,78 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of basic Ghidra scripting in Python
|
||||
# @category: Examples.Python
|
||||
# @runtime Jython
|
||||
|
||||
# Get info about the current program
|
||||
print
|
||||
print "Program Info:"
|
||||
program_name = currentProgram.getName()
|
||||
creation_date = currentProgram.getCreationDate()
|
||||
language_id = currentProgram.getLanguageID()
|
||||
compiler_spec_id = currentProgram.getCompilerSpec().getCompilerSpecID()
|
||||
print "%s: %s_%s (%s)\n" % (program_name, language_id, compiler_spec_id, creation_date)
|
||||
|
||||
# Get info about the current program's memory layout
|
||||
print "Memory layout:"
|
||||
print "Imagebase: " + hex(currentProgram.getImageBase().getOffset())
|
||||
for block in getMemoryBlocks():
|
||||
start = block.getStart().getOffset()
|
||||
end = block.getEnd().getOffset()
|
||||
print "%s [start: 0x%x, end: 0x%x]" % (block.getName(), start, end)
|
||||
print
|
||||
|
||||
# Get the current program's function names
|
||||
function = getFirstFunction()
|
||||
while function is not None:
|
||||
print function.getName()
|
||||
function = getFunctionAfter(function)
|
||||
print
|
||||
|
||||
# Get the address of the current program's current location
|
||||
print "Current location: " + hex(currentLocation.getAddress().getOffset())
|
||||
|
||||
# Get some user input
|
||||
val = askString("Hello", "Please enter a value")
|
||||
print val
|
||||
|
||||
# Output to a popup window
|
||||
popup(val)
|
||||
|
||||
# Add a comment to the current program
|
||||
minAddress = currentProgram.getMinAddress()
|
||||
listing = currentProgram.getListing()
|
||||
codeUnit = listing.getCodeUnitAt(minAddress)
|
||||
codeUnit.setComment(codeUnit.PLATE_COMMENT, "This is an added comment!")
|
||||
|
||||
# Get a data type from the user
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType != None: print "Chosen data type: " + str(dataType)
|
||||
|
||||
# Report progress to the GUI. Do this in all script loops!
|
||||
import time
|
||||
monitor.initialize(10)
|
||||
for i in range(10):
|
||||
monitor.checkCanceled() # check to see if the user clicked cancel
|
||||
time.sleep(1) # pause a bit so we can see progress
|
||||
monitor.incrementProgress(1) # update the progress
|
||||
monitor.setMessage("Working on " + str(i)) # update the status message
|
||||
@@ -1,54 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of Jython-specific functionality
|
||||
# @category: Examples.Python
|
||||
# @runtime Jython
|
||||
|
||||
# Using Java data structures from Jython
|
||||
python_list = [1, 2, 3]
|
||||
java_list = java.util.LinkedList(java.util.Arrays.asList(1, 2, 3))
|
||||
print str(type(python_list))
|
||||
print str(type(java_list))
|
||||
|
||||
# Importing Java packages for simpler Java calls
|
||||
from java.util import LinkedList, Arrays
|
||||
python_list = [1, 2, 3]
|
||||
java_list = LinkedList(Arrays.asList(1, 2, 3))
|
||||
print str(type(python_list))
|
||||
print str(type(java_list))
|
||||
|
||||
# Python adds helpful syntax to Java data structures
|
||||
print python_list[0]
|
||||
print java_list[0] # can't normally do this in java
|
||||
print java_list[0:2] # can't normally do this in java
|
||||
|
||||
# Iterate over Java collection the Python way
|
||||
for entry in java_list:
|
||||
print entry
|
||||
|
||||
# "in" keyword compatibility
|
||||
print str(3 in java_list)
|
||||
|
||||
# Create GUI with Java Swing
|
||||
from javax.swing import JFrame
|
||||
frame = JFrame() # don't call constructor with "new"
|
||||
frame.setSize(400,400)
|
||||
frame.setLocation(200, 200)
|
||||
frame.setTitle("Jython JFrame")
|
||||
frame.setVisible(True)
|
||||
|
||||
# Use JavaBean properties in constructor with keyword arguments!
|
||||
JFrame(title="Super Jython JFrame", size=(400,400), visible=True)
|
||||
@@ -1,118 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
# Examples of basic Python
|
||||
# @category: Examples.Python
|
||||
# @runtime Jython
|
||||
|
||||
# Python data types
|
||||
my_int = 32
|
||||
print my_int
|
||||
print hex(my_int)
|
||||
|
||||
my_bool = True
|
||||
print my_bool
|
||||
print not my_bool
|
||||
|
||||
my_string = 'this is a string'
|
||||
print my_string
|
||||
print my_string[:4]
|
||||
print my_string[-5:]
|
||||
print type(my_string)
|
||||
|
||||
my_list = ["a", 2, 5.3, my_string]
|
||||
print my_list
|
||||
print my_list[1]
|
||||
print my_list[1:2]
|
||||
print my_list + [1, 2, 3]
|
||||
print type(my_list)
|
||||
|
||||
my_tuple = (1, 2, 3)
|
||||
print my_tuple
|
||||
print my_tuple + (4,)
|
||||
print type(my_tuple)
|
||||
|
||||
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
|
||||
print my_dictionary["key3"]
|
||||
print type(my_dictionary)
|
||||
|
||||
my_null = None
|
||||
print my_null
|
||||
print type(my_null)
|
||||
|
||||
# Python conditionals
|
||||
if len(my_string) == 16:
|
||||
print "length of my_string is 16!"
|
||||
|
||||
if 4 not in my_list:
|
||||
print "4 is not in my_list!"
|
||||
|
||||
if type(my_dictionary) == type(dict):
|
||||
print "my_dictionary is a dictionary!"
|
||||
|
||||
if my_null is None and 2 + 2 == 4:
|
||||
print "my_null is None and 2 + 2 == 4!"
|
||||
|
||||
# Python loops
|
||||
for i in range(1, 10):
|
||||
print i
|
||||
|
||||
for letter in "word":
|
||||
print letter
|
||||
|
||||
for element in [100, 200, 300]:
|
||||
print element
|
||||
|
||||
for key in my_dictionary:
|
||||
print "%s:%s" % (key, my_dictionary[key])
|
||||
|
||||
i = 5
|
||||
while i < 8:
|
||||
print i
|
||||
i += 1
|
||||
|
||||
# Python functions
|
||||
def factorial(n):
|
||||
if n == 0:
|
||||
return 1
|
||||
return n * factorial(n-1)
|
||||
|
||||
i = 4
|
||||
print str(i) + "! = " + str(factorial(4))
|
||||
|
||||
# Python exceptions
|
||||
def error_function():
|
||||
raise IOError("An IO error occurred!")
|
||||
|
||||
try:
|
||||
error_function()
|
||||
print "I won't print"
|
||||
except IOError as e:
|
||||
print e.message
|
||||
|
||||
# Python class
|
||||
class Employee:
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
def getId(self):
|
||||
return self.id
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
e = Employee(5555, "Snoopy")
|
||||
print e.getName()
|
||||
@@ -1,194 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
"""
|
||||
Ties the Ghidra documentation into the builtin Python help.
|
||||
"""
|
||||
|
||||
import __builtin__
|
||||
import java
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from ghidra.framework import Application
|
||||
from ghidra.util import SystemUtilities
|
||||
|
||||
class _Helper:
|
||||
def __init__(self):
|
||||
self.orig_help = __builtin__.help
|
||||
if SystemUtilities.isInHeadlessMode():
|
||||
# ./pythonRun scenario
|
||||
self.msg = "\nExample workflow:\n"
|
||||
self.msg += " # Import headless analyzer\n"
|
||||
self.msg += " from ghidra.app.util.headless import HeadlessAnalyzer\n\n"
|
||||
self.msg += " # View HeadlessAnalyzer API\n"
|
||||
self.msg += " help(HeadlessAnalyzer)\n\n"
|
||||
self.msg += " # Get a HeadlessAnalyzer instance\n"
|
||||
self.msg += " headless = HeadlessAnalyzer.getInstance()\n\n"
|
||||
self.msg += " # Get headless options\n"
|
||||
self.msg += " options = headless.getOptions()\n\n"
|
||||
self.msg += " # View HeadlessOptions API and set options accordingly\n"
|
||||
self.msg += " help(options)\n\n"
|
||||
self.msg += " # View processLocal method API\n"
|
||||
self.msg += " help(headless.processLocal)\n\n"
|
||||
self.msg += " # Perform headless processing\n"
|
||||
self.msg += " headless.processLocal(...)\n\n"
|
||||
else:
|
||||
# PythonPlugin scenario
|
||||
self.msg = "Press 'F1' for usage instructions"
|
||||
|
||||
def __call__(self, param=None):
|
||||
|
||||
def get_class_and_method(param):
|
||||
if param is None and not SystemUtilities.isInHeadlessMode():
|
||||
# Enable help() in PythonPlugin scenario to show help for GhidraScript
|
||||
return "ghidra.app.script.GhidraScript", None
|
||||
class_name = None
|
||||
method_name = None
|
||||
if type(param) in [type(1), type(1j), type(1L), type(1.0), type(None), type(True), type([]), type({}), type(()), type({1})]:
|
||||
# These are instances of builtin types, so skip
|
||||
pass
|
||||
elif type(param) == type(str):
|
||||
# These are builtin Python types, so skip
|
||||
pass
|
||||
elif type(param) == type(str.split):
|
||||
# These are python functions, so skip
|
||||
pass
|
||||
elif type(param) == type(java):
|
||||
# These are java packages, which we don't don't document, so skip
|
||||
pass
|
||||
elif type(param) == type(java.lang.Object):
|
||||
# This is a java class, so extract its class name
|
||||
match = re.search("'(.*)'", str(param))
|
||||
if match is not None:
|
||||
class_name = match.group(1)
|
||||
elif type(param) == type(java.lang.Object().toString):
|
||||
# This is a java method, so extract its class name and method name
|
||||
tokens = str(param).split(" ")[2].split(".")
|
||||
class_name = ".".join(tokens[:-1])
|
||||
method_name = tokens[-1]
|
||||
else:
|
||||
# Assuming this is a java object, so extract its class name
|
||||
match = re.search("'(.*)'", str(type(param)))
|
||||
if match is not None:
|
||||
class_name = match.group(1)
|
||||
return class_name, method_name
|
||||
|
||||
def get_jsondoc(class_name):
|
||||
jsondoc = None
|
||||
try:
|
||||
root = Application.getApplicationRootDirectory().getFile(False).getParentFile().getAbsolutePath()
|
||||
javadoc_zip_name = "GhidraAPI_javadoc.zip"
|
||||
if SystemUtilities.isInDevelopmentMode():
|
||||
javadoc_zip = root + "/build/tmp/" + javadoc_zip_name
|
||||
else:
|
||||
javadoc_zip = root + "/docs/" + javadoc_zip_name
|
||||
if os.path.exists(javadoc_zip):
|
||||
json_path = "api/" + class_name.replace('.', '/') + '.json'
|
||||
with zipfile.ZipFile(javadoc_zip, "r").open(json_path) as f:
|
||||
jsondoc = json.load(f)
|
||||
except (IOError, KeyError) as e:
|
||||
pass
|
||||
return jsondoc
|
||||
|
||||
def format_class(cls):
|
||||
sig = "class " + cls["name"] + "\n"
|
||||
if "extends" in cls:
|
||||
sig += " extends " + cls["extends"] + "\n"
|
||||
implements = ""
|
||||
for interface in cls["implements"]:
|
||||
if len(implements) > 0:
|
||||
implements += ", "
|
||||
implements += interface
|
||||
if len(implements) > 0:
|
||||
sig += " implements " + implements + " \n"
|
||||
sig += "\n" + cls["comment"]
|
||||
return sig
|
||||
|
||||
def format_field(field):
|
||||
sig = "%s %s" % (field["type_long"], field["name"])
|
||||
if field["static"]:
|
||||
sig = "static " + sig
|
||||
if field["access"]:
|
||||
sig = field["access"] + " " + sig
|
||||
if field["constant_value"]:
|
||||
sig += " = " + field["constant_value"]
|
||||
sig += "\n"
|
||||
desc = " %s\n" % (field["comment"]) if len(field["comment"]) > 0 else ""
|
||||
return sig + desc
|
||||
|
||||
def format_method(method):
|
||||
paramsig = ""
|
||||
args = ""
|
||||
for param in method["params"]:
|
||||
if len(paramsig) > 0:
|
||||
paramsig += ", "
|
||||
paramsig += "%s %s" % (param["type_short"], param["name"])
|
||||
args += " @param %s (%s): %s\n" % (param["name"], param["type_long"], param["comment"])
|
||||
throws = ""
|
||||
for exception in method["throws"]:
|
||||
throws += " @throws %s: %s\n" % (exception["type_short"], exception["comment"])
|
||||
sig = "%s %s(%s)\n" % (method["return"]["type_short"], method["name"], paramsig)
|
||||
if method["static"]:
|
||||
sig = "static " + sig
|
||||
if method["access"]:
|
||||
sig = method["access"] + " " + sig
|
||||
desc = " %s\n\n" % (method["comment"]) if len(method["comment"]) > 0 else ""
|
||||
ret = ""
|
||||
if method["return"]["type_short"] != "void":
|
||||
ret = " @return %s: %s\n" % (method["return"]["type_long"], method["return"]["comment"])
|
||||
return sig + desc + args + ret + throws
|
||||
|
||||
class_name, method_name = get_class_and_method(param)
|
||||
if class_name is None:
|
||||
self.orig_help(param)
|
||||
else:
|
||||
try_again = True
|
||||
while try_again:
|
||||
try_again = False
|
||||
print "Searching API for " + class_name + ("" if method_name is None else "." + method_name + "()") + "..."
|
||||
jsondoc = get_jsondoc(class_name)
|
||||
if jsondoc is None:
|
||||
print "No API found for " + class_name
|
||||
elif method_name is None:
|
||||
print "#####################################################"
|
||||
print format_class(jsondoc)
|
||||
print "#####################################################\n"
|
||||
for field in jsondoc["fields"]:
|
||||
print format_field(field)
|
||||
print "-----------------------------------------------------"
|
||||
for method in jsondoc["methods"]:
|
||||
print format_method(method)
|
||||
print "-----------------------------------------------------"
|
||||
else:
|
||||
found_method = False
|
||||
for method in jsondoc["methods"]:
|
||||
if method["name"] == method_name:
|
||||
print "-----------------------------------------------------"
|
||||
print format_method(method)
|
||||
print "-----------------------------------------------------"
|
||||
found_method = True
|
||||
if not found_method:
|
||||
# The method may be inherited, so check for a super class and try again
|
||||
if "extends" in jsondoc:
|
||||
class_name = jsondoc["extends"]
|
||||
try_again = True
|
||||
|
||||
def __repr__(self):
|
||||
return self.msg
|
||||
|
||||
__builtin__.help = _Helper()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,237 +0,0 @@
|
||||
## ###
|
||||
# IP: LGPL 2.1
|
||||
##
|
||||
"""Extend introspect.py for Java based Jython classes."""
|
||||
|
||||
from introspect import *
|
||||
import string
|
||||
import __builtin__
|
||||
import java # needed for java.lang.Class
|
||||
import org # for org.python.core
|
||||
import ghidra # for JythonCodeCompletionFactory
|
||||
|
||||
__author__ = "Don Coleman <dcoleman@chariotsolutions.com>"
|
||||
|
||||
#def getAutoCompleteList(command='', locals=None, includeMagic=1,
|
||||
# includeSingle=1, includeDouble=1):
|
||||
# """Return list of auto-completion options for command.
|
||||
#
|
||||
# The list of options will be based on the locals namespace."""
|
||||
# attributes = []
|
||||
# # Get the proper chunk of code from the command.
|
||||
# root = getRoot(command, terminator='.')
|
||||
# try:
|
||||
# if locals is not None:
|
||||
# object = eval(root, locals)
|
||||
# else:
|
||||
# object = eval(root)
|
||||
# except:
|
||||
# #print "could not eval(", root, "):", sys.exc_info()[0]
|
||||
# return attributes
|
||||
#
|
||||
# if ispython(object):
|
||||
# # use existing code
|
||||
# attributes = getAttributeNames(object, includeMagic, includeSingle,
|
||||
# includeDouble)
|
||||
# else:
|
||||
# methods = methodsOf(object.__class__)
|
||||
# attributes = [eachMethod.__name__ for eachMethod in methods]
|
||||
#
|
||||
# return attributes
|
||||
#
|
||||
#def methodsOf(clazz):
|
||||
# """Return a list of all the methods in a class"""
|
||||
# classMembers = vars(clazz).values()
|
||||
# methods = [eachMember for eachMember in classMembers
|
||||
# if callable(eachMember)]
|
||||
# for eachBase in clazz.__bases__:
|
||||
# methods.extend(methodsOf(eachBase))
|
||||
# return methods
|
||||
|
||||
def getCallTipJava(command='', locals=None):
|
||||
"""For a command, return a tuple of object name, argspec, tip text.
|
||||
|
||||
The call tip information will be based on the locals namespace."""
|
||||
|
||||
calltip = ('', '', '') # object name, argspec, tip text.
|
||||
|
||||
# Get the proper chunk of code from the command.
|
||||
(root, filter) = getRootAndFilter(command, terminator='(')
|
||||
#java.lang.System.out.println("root=" + root)
|
||||
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
#java.lang.System.err.println("could not eval(" + root + "):" +
|
||||
# str(sys.exc_info()[0]))
|
||||
return calltip
|
||||
|
||||
if ispython(object):
|
||||
# Patrick's code handles Python code
|
||||
# TODO fix in future because getCallTip runs eval() again
|
||||
#java.lang.System.out.println("is a Python object")
|
||||
calltip = getCallTip(command, locals)
|
||||
|
||||
if not calltip[1] and not calltip[2]:
|
||||
# either it's a pure Java object, or we didn't get much from Python's
|
||||
# getCallTip
|
||||
name = ''
|
||||
try:
|
||||
name = object.__name__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
tipList = []
|
||||
argspec = '' # not using argspec for Java
|
||||
|
||||
# if inspect.isbuiltin(object):
|
||||
# # inspect.isbuiltin() fails for Jython
|
||||
# # Can we get the argspec for Jython builtins? We can't in Python.
|
||||
# # YES!
|
||||
# print "is a builtin"
|
||||
# pass
|
||||
# elif inspect.isclass(object):
|
||||
if inspect.isclass(object):
|
||||
# get the constructor(s)
|
||||
# TODO consider getting modifiers since Jython can access
|
||||
# private methods
|
||||
#java.lang.System.out.println("is a class")
|
||||
try:
|
||||
# this will likely fail for pure Python classes
|
||||
constructors = object.getConstructors()
|
||||
for constructor in constructors:
|
||||
paramList = []
|
||||
paramTypes = constructor.getParameterTypes()
|
||||
# paramTypes is an array of classes; we need Strings
|
||||
# TODO consider list comprehension
|
||||
for param in paramTypes:
|
||||
# TODO translate [B to byte[], C to char[], etc.
|
||||
paramList.append(param.__name__)
|
||||
paramString = string.join(paramList, ', ')
|
||||
tip = "%s(%s)" % (constructor.name, paramString)
|
||||
tipList.append(tip)
|
||||
if len(constructors) == 1:
|
||||
plural = ""
|
||||
else:
|
||||
plural = "s"
|
||||
name = "Constructor" + plural + " for " + name + ":"
|
||||
except:
|
||||
pass
|
||||
# if callable(object):
|
||||
# # some Python types are function names as well, like
|
||||
# # type() and file()
|
||||
# argspec = str(object.__call__)
|
||||
# # these don't seem to be very accurate
|
||||
# if hasattr(object.__call__, "maxargs"):
|
||||
# tipList.append("maxargs?: " +
|
||||
# str(object.__call__.maxargs))
|
||||
# if hasattr(object.__call__, "minargs"):
|
||||
# tipList.append("minargs?: " +
|
||||
# str(object.__call__.minargs))
|
||||
# elif inspect.ismethod(object):
|
||||
elif inspect.isroutine(object):
|
||||
#java.lang.System.out.println("is a routine")
|
||||
# method = object
|
||||
# object = method.im_class
|
||||
#
|
||||
# # Java allows overloading so we may have more than one method
|
||||
# methodArray = object.getMethods()
|
||||
#
|
||||
# for eachMethod in methodArray:
|
||||
# if eachMethod.name == method.__name__:
|
||||
# paramList = []
|
||||
# for eachParam in eachMethod.parameterTypes:
|
||||
# paramList.append(eachParam.__name__)
|
||||
#
|
||||
# paramString = string.join(paramList, ', ')
|
||||
#
|
||||
# # create a Python style string a la PyCrust
|
||||
# # we're showing the parameter type rather than the
|
||||
# # parameter name, since that's all we can get
|
||||
# # we need to show multiple methods for overloading
|
||||
# # TODO improve message format
|
||||
# # do we want to show the method visibility?
|
||||
# # how about exceptions?
|
||||
# # note: name, return type and exceptions same for
|
||||
# # EVERY overloaded method
|
||||
#
|
||||
#
|
||||
# tip = "%s(%s) -> %s" % (eachMethod.name, paramString,
|
||||
# eachMethod.returnType)
|
||||
# tipList.append(tip)
|
||||
if hasattr(object, "argslist"):
|
||||
for args in object.argslist:
|
||||
if args is not None:
|
||||
# for now
|
||||
tipList.append(str(args.method))
|
||||
# elif callable(object):
|
||||
# argspec = str(object.__call__)
|
||||
# # these don't seem to be very accurate
|
||||
# if hasattr(object.__call__, "maxargs"):
|
||||
# tipList.append("maxargs?: " + str(object.__call__.maxargs))
|
||||
# if hasattr(object.__call__, "minargs"):
|
||||
# tipList.append("minargs?: " + str(object.__call__.minargs))
|
||||
|
||||
# elif inspect.isfunction(object):
|
||||
# print "is function"
|
||||
|
||||
if (len(tipList) == 0):
|
||||
if hasattr(object, "__name__") and \
|
||||
hasattr(__builtin__, object.__name__):
|
||||
# try to get arguments for any other "old-style" builtin
|
||||
# functions (see __builtin__.java, classDictInit() method)
|
||||
methods = \
|
||||
java.lang.Class.getMethods(org.python.core.__builtin__)
|
||||
for method in methods:
|
||||
if method.name == object.__name__:
|
||||
tipList.append(str(method))
|
||||
argspec = "a built-in Python function"
|
||||
else:
|
||||
# last-ditch: try possible __call__ methods of new-style
|
||||
# objects
|
||||
for possible_call_method in \
|
||||
ghidra.python.PythonCodeCompletionFactory.getCallMethods(object):
|
||||
signature = str(possible_call_method)
|
||||
# clean up the method signature a bit, so it looks sane
|
||||
signature = \
|
||||
signature.replace("$1exposed_", ".").replace(".__call__", "")
|
||||
tipList.append(signature)
|
||||
|
||||
calltip = (name, argspec, string.join(tipList, "\n"))
|
||||
return calltip
|
||||
|
||||
def ispython(object):
|
||||
"""
|
||||
Figure out if this is Python code or Java code..
|
||||
"""
|
||||
pyclass = 0
|
||||
pycode = 0
|
||||
pyinstance = 0
|
||||
|
||||
if inspect.isclass(object):
|
||||
try:
|
||||
object.__doc__
|
||||
pyclass = 1
|
||||
except AttributeError:
|
||||
pyclass = 0
|
||||
elif inspect.ismethod(object):
|
||||
try:
|
||||
# changed for Jython 2.2a1
|
||||
#object.__dict__
|
||||
object.__str__
|
||||
pycode = 1
|
||||
except AttributeError:
|
||||
pycode = 0
|
||||
else: # I guess an instance of an object falls here
|
||||
try:
|
||||
# changed for Jython 2.2a1
|
||||
#object.__dict__
|
||||
object.__str__
|
||||
pyinstance = 1
|
||||
except AttributeError:
|
||||
pyinstance = 0
|
||||
|
||||
return pyclass | pycode | pyinstance
|
||||
@@ -1,48 +0,0 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
"""
|
||||
User-supplied customizations go here.
|
||||
"""
|
||||
|
||||
# nice-to-have: place 'java' and 'ghidra' into the local namespace, so you
|
||||
# can do fun things like "dir(java.lang)"
|
||||
import java
|
||||
import ghidra
|
||||
import __main__
|
||||
__main__.java = java
|
||||
__main__.ghidra = ghidra
|
||||
|
||||
# fix Jython "bug": unknown type 'javainstance' or 'javapackage' even though
|
||||
# that is the type Jython gives us if we ask type(<someObject>) or
|
||||
# type(ghidra) (respectively)
|
||||
import __builtin__
|
||||
import org.python.core
|
||||
# changed by Jim 20090528 for Jython 2.5
|
||||
# not sure why I even put these here... the first one might be troublesome
|
||||
#__builtin__.javainstance = org.python.core.PyJavaInstance
|
||||
#__builtin__.javapackage = org.python.core.PyJavaPackage
|
||||
#__builtin__.javaclass = org.python.core.PyJavaClass
|
||||
__builtin__.javainstance = org.python.core.PyObjectDerived
|
||||
__builtin__.javapackage = org.python.core.PyJavaPackage
|
||||
__builtin__.javaclass = org.python.core.PyJavaType
|
||||
|
||||
# changed by Jim 20090528 for Jython 2.5
|
||||
# REMOVED collections stuff
|
||||
# OOPS still need this
|
||||
import sys
|
||||
|
||||
# Ghidra documentation
|
||||
import ghidradoc
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version='1.0' encoding='ISO-8859-1' ?>
|
||||
|
||||
<tocroot>
|
||||
<tocref id="Ghidra Functionality">
|
||||
<tocref id="Scripting">
|
||||
|
||||
<!-- The sort group places this entry at the bottom of all other entries in the Program
|
||||
Annotation section. 'z' puts it at the end. The rest makes it unique. -->
|
||||
<tocdef id="Jython Interpreter" sortgroup="z_Python_Jython" text="Jython Interpreter" target="help/topics/Jython/interpreter.html" />
|
||||
</tocref>
|
||||
</tocref>
|
||||
</tocroot>
|
||||
@@ -1,171 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Jython Interpreter</TITLE>
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="Jython"></A>Jython Interpreter</H1>
|
||||
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> provides a full general-purpose Jython interactive shell
|
||||
and allows you to interact with your current Ghidra session by exposing Ghidra's powerful Java
|
||||
API through the magic of Jython.
|
||||
</P>
|
||||
|
||||
<H2>Environment</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> is configured to run in a similar context as a Ghidra
|
||||
script. Therefore, you immediately have access to variables such as <TT>currentProgram</TT>,
|
||||
<TT>currentSelection</TT>, <TT>currentAddress</TT>, etc without needing to import them.
|
||||
These variables exist as Java objects behind the scenes, but Jython allows you to interact with
|
||||
them through a Python interface, which is similar to Java in some ways.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
As in Java, classes outside of your current package/module need to be explicitly imported.
|
||||
For example, consider the following code snippet:
|
||||
</P>
|
||||
<BR>
|
||||
|
||||
<PRE>
|
||||
<FONT COLOR="GREEN"># Get a data type from the user</FONT>
|
||||
tool = state.getTool()
|
||||
dtm = currentProgram.getDataTypeManager()
|
||||
from ghidra.app.util.datatype import DataTypeSelectionDialog
|
||||
from ghidra.util.data.DataTypeParser import AllowedDataTypes
|
||||
selectionDialog = DataTypeSelectionDialog(tool, dtm, -1, AllowedDataTypes.FIXED_LENGTH)
|
||||
tool.showDialog(selectionDialog)
|
||||
dataType = selectionDialog.getUserChosenDataType()
|
||||
if dataType != None: print "Chosen data type: " + str(dataType)
|
||||
</PRE>
|
||||
|
||||
<P>
|
||||
<TT>currentProgram</TT> and <TT>state</TT> are defined within the Ghidra scripting class
|
||||
hierarchy, so nothing has to be explicitly imported before they can be used. However, because
|
||||
the <TT>DataTypeSelectionDialog</TT> class and <TT>AllowedDataType</TT> enum reside in
|
||||
different packages, they must be explicitly imported. Failure to do so will result in a
|
||||
Jython <TT><FONT COLOR="RED">NameError</FONT></TT>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Clear_Interpreter"></A>Clear <IMG border="0" src="images/erase16.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command clears the interpreter's display. Its effect is purely visual.
|
||||
It does not affect the state of the interpreter in any way.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Interrupt_Interpreter"></A>Interrupt <IMG border="0" src="images/dialog-cancel.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command issues a keyboard interrupt to the interpreter, which can be used to interrupt
|
||||
long running commands or loops.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Reset_Interpreter"></A>Reset <IMG border="0" src="images/reload3.png"></H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
This command resets the interpreter, which clears the display and resets all state.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Keybindings</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The Ghidra <I>Jython Interpreter</I> supports the following hard-coded keybindings:
|
||||
<UL>
|
||||
<LI><B>(up):</B> 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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,301 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import org.python.core.PyInstance;
|
||||
import org.python.core.PyObject;
|
||||
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Generates CodeCompletions from Jython objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class JythonCodeCompletionFactory {
|
||||
private static List<Class<?>> classes = new ArrayList<>();
|
||||
private static Map<Class<?>, Color> classToColorMap = new HashMap<>();
|
||||
/* necessary because we only want to show the user the simple class name
|
||||
* Well, that, and the Options.DELIMITER is a '.' which totally messes
|
||||
* things up.
|
||||
*/
|
||||
private static Map<String, Class<?>> simpleNameToClass = new HashMap<>();
|
||||
private static Map<Class<?>, String> classDescription = new HashMap<>();
|
||||
public static final String COMPLETION_LABEL = "Code Completion Colors";
|
||||
|
||||
/* package-level accessibility so that JythonPlugin can tell this is
|
||||
* our option
|
||||
*/
|
||||
final static String INCLUDE_TYPES_LABEL = "Include type names in code completion popup?";
|
||||
private final static String INCLUDE_TYPES_DESCRIPTION =
|
||||
"Whether or not to include the type names (classes) of the possible " +
|
||||
"completions in the code completion window. The class name will be " +
|
||||
"parenthesized after the completion.";
|
||||
private final static boolean INCLUDE_TYPES_DEFAULT = true;
|
||||
private static boolean includeTypes = INCLUDE_TYPES_DEFAULT;
|
||||
|
||||
//@formatter:off
|
||||
public static final Color NULL_COLOR = new GColor("color.fg.plugin.jython.syntax.null");
|
||||
public static final Color FUNCTION_COLOR = new GColor("color.fg.plugin.jython.syntax.function");
|
||||
public static final Color PACKAGE_COLOR = new GColor("color.fg.plugin.jython.syntax.package");
|
||||
public static final Color CLASS_COLOR = new GColor("color.fg.plugin.jython.syntax.class");
|
||||
public static final Color METHOD_COLOR = new GColor("color.fg.plugin.jython.syntax.method");
|
||||
/* anonymous code chunks */
|
||||
public static final Color CODE_COLOR = new GColor("color.fg.plugin.jython.syntax.code");
|
||||
public static final Color INSTANCE_COLOR = new GColor("color.fg.plugin.jython.syntax.instance");
|
||||
public static final Color SEQUENCE_COLOR = new GColor("color.fg.plugin.jython.syntax.sequence");
|
||||
public static final Color MAP_COLOR = new GColor("color.fg.plugin.jython.syntax.map");
|
||||
public static final Color NUMBER_COLOR = new GColor("color.fg.plugin.jython.syntax.number");
|
||||
/* for weird Jython-specific stuff */
|
||||
public static final Color SPECIAL_COLOR = new GColor("color.fg.plugin.jython.syntax.special");
|
||||
//@formatter:on
|
||||
|
||||
static {
|
||||
/* Order matters! This is the order in which classes are checked for
|
||||
* coloring.
|
||||
*/
|
||||
setupClass("org.python.core.PyNone", NULL_COLOR, "'None' (null) Objects");
|
||||
|
||||
setupClass("org.python.core.PyReflectedFunction", FUNCTION_COLOR,
|
||||
"Python functions written in Java");
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.BuiltinFunctions", FUNCTION_COLOR,
|
||||
// "Python's built-in functions collection (note that many are " +
|
||||
// "re-implemented in Java)");
|
||||
setupClass("org.python.core.__builtin__", FUNCTION_COLOR,
|
||||
"Python's built-in functions collection (note that many are " +
|
||||
"re-implemented in Java)");
|
||||
setupClass("org.python.core.PyFunction", FUNCTION_COLOR, "functions written in Python");
|
||||
setupClass("org.python.core.PyMethodDescr", FUNCTION_COLOR,
|
||||
"unbound Python builtin instance methods (they take an " +
|
||||
"Object as the first argument)");
|
||||
|
||||
setupClass("org.python.core.PyJavaPackage", PACKAGE_COLOR, "Java packages");
|
||||
setupClass("org.python.core.PyModule", PACKAGE_COLOR, "Python modules");
|
||||
|
||||
/* Even though the latter is a subclass of the former, this allows
|
||||
* the user to differentiate visually Java classes from Python classes
|
||||
* if they so wish. But we don't do this by default.
|
||||
*/
|
||||
/* changed for Jython 2.5 */
|
||||
// setupClass("org.python.core.PyJavaClass", CLASS_COLOR,
|
||||
// "Java classes");
|
||||
setupClass("org.python.core.PyJavaType", CLASS_COLOR, "Java classes");
|
||||
setupClass("org.python.core.PyClass", CLASS_COLOR, "Python classes");
|
||||
setupClass("org.python.core.PyType", CLASS_COLOR, "core Python types");
|
||||
|
||||
setupClass("org.python.core.PyMethod", METHOD_COLOR, "methods");
|
||||
setupClass("org.python.core.PyBuiltinFunction", METHOD_COLOR,
|
||||
"core Python methods, often inherited from Python's Object " +
|
||||
"(overriding these methods is very powerful)");
|
||||
|
||||
setupClass("org.python.core.PySequence", SEQUENCE_COLOR,
|
||||
"iterable sequences, including arrays, list, and strings");
|
||||
|
||||
setupClass("org.python.core.PyDictionary", MAP_COLOR, "arbitrary Python mapping type");
|
||||
setupClass("org.python.core.PyStringMap", MAP_COLOR, "Python String->Object mapping type");
|
||||
|
||||
setupClass("org.python.core.PyInteger", NUMBER_COLOR, "integers");
|
||||
setupClass("org.python.core.PyLong", NUMBER_COLOR, "long integers");
|
||||
setupClass("org.python.core.PyFloat", NUMBER_COLOR, "floating-point (decimal) numbers");
|
||||
setupClass("org.python.core.PyComplex", NUMBER_COLOR, "complex numbers");
|
||||
|
||||
setupClass("org.python.core.PyCompoundCallable", SPECIAL_COLOR,
|
||||
"special Python properties for " +
|
||||
"assigning Python functions as EventListeners on Java objects");
|
||||
|
||||
/* changed for Jython 2.5 */
|
||||
setupClass("org.python.core.PyObjectDerived", INSTANCE_COLOR, "Java Objects");
|
||||
setupClass("org.python.core.PyInstance", INSTANCE_COLOR, "Python Objects");
|
||||
|
||||
setupClass("org.python.core.PyCode", CODE_COLOR, "chunks of Python code");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param klass a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(Class<?> klass) {
|
||||
return getSimpleName(klass.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual class name for a Class.
|
||||
*
|
||||
* @param className name of a Class
|
||||
* @return The actual class name.
|
||||
*/
|
||||
private static String getSimpleName(String className) {
|
||||
/* lastIndexOf returns -1 on not found, so this works whether or not
|
||||
* a period is actually in className
|
||||
*/
|
||||
return className.substring(className.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a Class mapping.
|
||||
*
|
||||
* @param className Class name
|
||||
* @param defaultColor default Color for this Class
|
||||
* @param description description of the Class
|
||||
*/
|
||||
private static void setupClass(String className, Color defaultColor, String description) {
|
||||
try {
|
||||
Class<?> klass = Class.forName(className);
|
||||
classes.add(klass);
|
||||
classToColorMap.put(klass, defaultColor);
|
||||
simpleNameToClass.put(getSimpleName(klass), klass);
|
||||
classDescription.put(klass, description);
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
Msg.debug(JythonCodeCompletionFactory.class, "Unable to find class: " + className,
|
||||
cnfe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CodeCompletion from the given Jython objects.
|
||||
*
|
||||
* @param description description of the new CodeCompletion
|
||||
* @param insertion what will be inserted to make the code complete
|
||||
* @param pyObj a Jython Object
|
||||
* @return A new CodeCompletion from the given Jython objects.
|
||||
* @deprecated use {@link #newCodeCompletion(String, String, PyObject, String)} instead,
|
||||
* it allows creation of substituting code completions
|
||||
*/
|
||||
@Deprecated
|
||||
public static CodeCompletion newCodeCompletion(String description, String insertion,
|
||||
PyObject pyObj) {
|
||||
return newCodeCompletion(description, insertion, pyObj, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CodeCompletion from the given Jython objects.
|
||||
*
|
||||
* @param description description of the new CodeCompletion
|
||||
* @param insertion what will be inserted to make the code complete
|
||||
* @param pyObj a Jython Object
|
||||
* @param userInput a word we want to complete, can be an empty string.
|
||||
* It's used to determine which part (if any) of the input should be
|
||||
* removed before the insertion of the completion
|
||||
* @return A new CodeCompletion from the given Jython objects.
|
||||
*/
|
||||
public static CodeCompletion newCodeCompletion(String description, String insertion,
|
||||
PyObject pyObj, String userInput) {
|
||||
JComponent comp = null;
|
||||
|
||||
if (pyObj != null) {
|
||||
if (includeTypes) {
|
||||
/* append the class name to the end of the description */
|
||||
String className = getSimpleName(pyObj.getClass());
|
||||
if (pyObj instanceof PyInstance) {
|
||||
/* get the real class */
|
||||
className = getSimpleName(((PyInstance) pyObj).instclass.__name__);
|
||||
}
|
||||
else if (className.startsWith("Py")) {
|
||||
/* strip off the "Py" */
|
||||
className = className.substring("Py".length());
|
||||
}
|
||||
description = description + " (" + className + ")";
|
||||
}
|
||||
|
||||
comp = new GDLabel(description);
|
||||
Iterator<Class<?>> iter = classes.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Class<?> testClass = iter.next();
|
||||
if (testClass.isInstance(pyObj)) {
|
||||
comp.setForeground(classToColorMap.get(testClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int charsToRemove = userInput.length();
|
||||
return new CodeCompletion(description, insertion, comp, charsToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up Jython code completion Options.
|
||||
* @param plugin jython plugin as options owner
|
||||
* @param options an Options handle
|
||||
*/
|
||||
public static void setupOptions(JythonPlugin plugin, Options options) {
|
||||
includeTypes = options.getBoolean(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT);
|
||||
options.registerOption(INCLUDE_TYPES_LABEL, INCLUDE_TYPES_DEFAULT, null,
|
||||
INCLUDE_TYPES_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an Option change.
|
||||
*
|
||||
* This is named slightly differently because it is a static method, not
|
||||
* an instance method.
|
||||
*
|
||||
* By the time we get here, we assume that the Option changed is indeed
|
||||
* ours.
|
||||
*
|
||||
* @param options the Options handle
|
||||
* @param name name of the Option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
public static void changeOptions(Options options, String name, Object oldValue,
|
||||
Object newValue) {
|
||||
if (name.equals(INCLUDE_TYPES_LABEL)) {
|
||||
includeTypes = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
else {
|
||||
Msg.error(JythonCodeCompletionFactory.class, "unknown option '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Java __call__ methods declared for a Jython object.
|
||||
*
|
||||
* Some Jython "methods" in the new-style Jython objects are actually
|
||||
* classes in and of themselves, re-implementing __call__ methods to
|
||||
* tell us how to call them. This returns an array of those Methods
|
||||
* (for code completion help).
|
||||
*
|
||||
* @param obj a PyObject
|
||||
* @return the Java __call__ methods declared for the Jython object
|
||||
*/
|
||||
public static Object[] getCallMethods(PyObject obj) {
|
||||
List<Method> callMethodList = new ArrayList<>();
|
||||
Method[] declaredMethods = obj.getClass().getDeclaredMethods();
|
||||
|
||||
for (Method declaredMethod : declaredMethods) {
|
||||
if (declaredMethod.getName().equals("__call__")) {
|
||||
callMethodList.add(declaredMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return callMethodList.toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.python.core.PySystemState;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
import generic.jar.ResourceFile;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.plugin.core.interpreter.*;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.script.ScriptControls;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.task.*;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* This plugin provides the interactive Jython interpreter.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Jython Interpreter",
|
||||
description = "Provides an interactive Jython Interpreter that is tightly integrated with a loaded Ghidra program.",
|
||||
servicesRequired = { InterpreterPanelService.class },
|
||||
isSlowInstallation = true
|
||||
)
|
||||
//@formatter:on
|
||||
public class JythonPlugin extends ProgramPlugin
|
||||
implements InterpreterConnection, OptionsChangeListener {
|
||||
|
||||
private InterpreterConsole console;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
private JythonScript interactiveScript;
|
||||
private TaskMonitor interactiveTaskMonitor;
|
||||
private JythonPluginInputThread inputThread;
|
||||
|
||||
// Plugin options
|
||||
private final static String INCLUDE_BUILTINS_LABEL = "Include \"builtins\" in code completion?";
|
||||
private final static String INCLUDE_BUILTINS_DESCRIPTION =
|
||||
"Whether or not to include Jython's built-in functions and properties in the pop-up code completion window.";
|
||||
private final static boolean INCLUDE_BUILTINS_DEFAULT = true;
|
||||
|
||||
private static final Icon ICON = new GIcon("icon.plugin.jython");
|
||||
|
||||
private boolean includeBuiltins = INCLUDE_BUILTINS_DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JythonPlugin} object.
|
||||
*
|
||||
* @param tool The tool associated with this plugin.
|
||||
*/
|
||||
public JythonPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interpreter console.
|
||||
*
|
||||
* @return The plugin's interpreter console.
|
||||
*/
|
||||
InterpreterConsole getConsole() {
|
||||
return console;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's Jython interpreter.
|
||||
*
|
||||
* @return The plugin's Jython interpreter. May be null.
|
||||
*/
|
||||
GhidraJythonInterpreter getInterpreter() {
|
||||
return interpreter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive script
|
||||
*
|
||||
* @return The plugin's interactive script.
|
||||
*/
|
||||
JythonScript getInteractiveScript() {
|
||||
return interactiveScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin's interactive task monitor.
|
||||
*
|
||||
* @return The plugin's interactive task monitor.
|
||||
*/
|
||||
TaskMonitor getInteractiveTaskMonitor() {
|
||||
return interactiveTaskMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
console =
|
||||
getTool().getService(InterpreterPanelService.class).createInterpreterPanel(this, false);
|
||||
console.addFirstActivationCallback(() -> {
|
||||
welcome();
|
||||
resetInterpreter();
|
||||
});
|
||||
createActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates various actions for the plugin.
|
||||
*/
|
||||
private void createActions() {
|
||||
|
||||
// Interrupt Interpreter
|
||||
DockingAction interruptAction = new DockingAction("Interrupt Interpreter", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
interrupt();
|
||||
}
|
||||
};
|
||||
interruptAction.setDescription("Interrupt Interpreter");
|
||||
interruptAction.setToolBarData(
|
||||
new ToolBarData(Icons.NOT_ALLOWED_ICON, null));
|
||||
interruptAction.setEnabled(true);
|
||||
interruptAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_I, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
|
||||
interruptAction.setHelpLocation(new HelpLocation(getTitle(), "Interrupt_Interpreter"));
|
||||
console.addAction(interruptAction);
|
||||
|
||||
// Reset Interpreter
|
||||
DockingAction resetAction = new DockingAction("Reset Interpreter", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
reset();
|
||||
}
|
||||
};
|
||||
resetAction.setDescription("Reset Interpreter");
|
||||
resetAction.setToolBarData(
|
||||
new ToolBarData(Icons.REFRESH_ICON, null));
|
||||
resetAction.setEnabled(true);
|
||||
resetAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_D, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
|
||||
resetAction.setHelpLocation(new HelpLocation(getTitle(), "Reset_Interpreter"));
|
||||
console.addAction(resetAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the interpreter to a new starting state. This is used when the plugin is first
|
||||
* initialized, as well as when an existing interpreter receives a Jython exit command.
|
||||
* We used to try to reset the same interpreter, but it was really hard to do that correctly
|
||||
* so we now just create a brand new one.
|
||||
* <p>
|
||||
* NOTE: Loading Jython for the first time can be quite slow the first time, so we do this
|
||||
* when the user wants to first interact with the interpreter (rather than when the plugin loads).
|
||||
*/
|
||||
private void resetInterpreter() {
|
||||
|
||||
TaskLauncher.launchModal("Resetting Jython...", () -> {
|
||||
resetInterpreterInBackground();
|
||||
});
|
||||
}
|
||||
|
||||
// we expect this to be called from off the Swing thread
|
||||
private void resetInterpreterInBackground() {
|
||||
|
||||
// Reset the interpreter by creating a new one. Clean up the old one if present.
|
||||
if (interpreter == null) {
|
||||
|
||||
// Setup options
|
||||
ToolOptions options = tool.getOptions("Jython");
|
||||
includeBuiltins = options.getBoolean(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT);
|
||||
options.registerOption(INCLUDE_BUILTINS_LABEL, INCLUDE_BUILTINS_DEFAULT, null,
|
||||
INCLUDE_BUILTINS_DESCRIPTION);
|
||||
options.addOptionsChangeListener(this);
|
||||
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
|
||||
// Setup code completion. This currently has to be done after the interpreter
|
||||
// is created. Otherwise an exception will occur.
|
||||
JythonCodeCompletionFactory.setupOptions(this, options);
|
||||
}
|
||||
else {
|
||||
inputThread.shutdown();
|
||||
inputThread = null;
|
||||
interpreter.cleanup();
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
}
|
||||
|
||||
// Reset the console.
|
||||
console.clear();
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
|
||||
// Tie the interpreter's input/output to the plugin's console.
|
||||
interpreter.setIn(console.getStdin());
|
||||
interpreter.setOut(console.getStdOut());
|
||||
interpreter.setErr(console.getStdErr());
|
||||
|
||||
// Print a welcome message.
|
||||
welcome();
|
||||
|
||||
// Setup the JythonScript describing the state of the interactive prompt.
|
||||
// This allows things like currentProgram and currentAddress to dynamically reflect
|
||||
// what's happening in the listing. Injecting the script hierarchy early here allows
|
||||
// code completion to work before commands are entered.
|
||||
interactiveScript = new JythonScript();
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), getCurrentProgram(), getProgramLocation(),
|
||||
getProgramSelection(), getProgramHighlight()),
|
||||
new ScriptControls(console, interactiveTaskMonitor));
|
||||
interpreter.injectScriptHierarchy(interactiveScript);
|
||||
interactiveTaskMonitor = new JythonInteractiveTaskMonitor(console.getStdOut());
|
||||
|
||||
// Start the input thread that receives jython commands to execute.
|
||||
inputThread = new JythonPluginInputThread(this);
|
||||
inputThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change in one of our options.
|
||||
*
|
||||
* @param options the options handle
|
||||
* @param optionName name of the option changed
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
*/
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
if (optionName.startsWith(JythonCodeCompletionFactory.COMPLETION_LABEL)) {
|
||||
JythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(JythonCodeCompletionFactory.INCLUDE_TYPES_LABEL)) {
|
||||
JythonCodeCompletionFactory.changeOptions(options, optionName, oldValue, newValue);
|
||||
}
|
||||
else if (optionName.equals(INCLUDE_BUILTINS_LABEL)) {
|
||||
includeBuiltins = ((Boolean) newValue).booleanValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible command completion values.
|
||||
*
|
||||
* @param cmd current command line (without prompt)
|
||||
* @return A list of possible command completion values. Could be empty if there aren't any.
|
||||
*/
|
||||
@Override
|
||||
public List<CodeCompletion> getCompletions(String cmd) {
|
||||
return getCompletions(cmd, cmd.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of possible command completion values at the given position.
|
||||
*
|
||||
* @param cmd current command line (without prompt)
|
||||
* @param caretPos The position of the caret in the input string 'cmd'
|
||||
* @return A list of possible command completion values. Could be empty if there aren't any.
|
||||
*/
|
||||
@Override
|
||||
public List<CodeCompletion> getCompletions(String cmd, int caretPos) {
|
||||
// Refresh the environment
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), currentProgram, currentLocation,
|
||||
currentSelection, currentHighlight),
|
||||
new ScriptControls(console, interactiveTaskMonitor));
|
||||
|
||||
return interpreter.getCommandCompletions(cmd, includeBuiltins, caretPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
// Terminate the input thread
|
||||
if (inputThread != null) {
|
||||
inputThread.shutdown();
|
||||
inputThread = null;
|
||||
}
|
||||
|
||||
// Dispose of the console
|
||||
if (console != null) {
|
||||
console.dispose();
|
||||
console = null;
|
||||
}
|
||||
|
||||
// Cleanup the interpreter
|
||||
if (interpreter != null) {
|
||||
interpreter.cleanup();
|
||||
interpreter = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupts what the interpreter is currently doing.
|
||||
*/
|
||||
public void interrupt() {
|
||||
if (interpreter == null) {
|
||||
return;
|
||||
}
|
||||
interpreter.interrupt(inputThread.getJythonPluginExecutionThread());
|
||||
console.setPrompt(interpreter.getPrimaryPrompt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the interpreter's state.
|
||||
*/
|
||||
public void reset() {
|
||||
|
||||
// Do an interrupt in case there is a loop or something running
|
||||
interrupt();
|
||||
|
||||
resetInterpreter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Jython";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPluginDescription().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a welcome message to the console.
|
||||
*/
|
||||
private void welcome() {
|
||||
console.getOutWriter().println("Jython Interpreter for Ghidra");
|
||||
console.getOutWriter().println("Based on Jython version " + PySystemState.version);
|
||||
console.getOutWriter().println("Press 'F1' for usage instructions");
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for cancelling execution using a TaskMonitor.
|
||||
*/
|
||||
class JythonInteractiveTaskMonitor extends TaskMonitorAdapter {
|
||||
private PrintWriter output = null;
|
||||
|
||||
public JythonInteractiveTaskMonitor(PrintWriter stdOut) {
|
||||
output = stdOut;
|
||||
}
|
||||
|
||||
public JythonInteractiveTaskMonitor(OutputStream stdout) {
|
||||
this(new PrintWriter(stdout, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
output.println("<jython-interactive>: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
import db.Transaction;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.script.ScriptControls;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a jython command for the plugin.
|
||||
*/
|
||||
class JythonPluginExecutionThread extends Thread {
|
||||
|
||||
private JythonPlugin plugin;
|
||||
private String cmd;
|
||||
private AtomicBoolean moreInputWanted;
|
||||
|
||||
/**
|
||||
* Creates a new jython plugin execution thread that executes the given command for the given
|
||||
* plugin.
|
||||
*
|
||||
* @param plugin The jython plugin to execute the command for.
|
||||
* @param cmd The jython command to execute.
|
||||
* @param moreInputWanted Gets set to indicate that the executed command expects more input.
|
||||
*/
|
||||
JythonPluginExecutionThread(JythonPlugin plugin, String cmd, AtomicBoolean moreInputWanted) {
|
||||
super("Jython plugin execution thread");
|
||||
|
||||
this.plugin = plugin;
|
||||
this.cmd = cmd;
|
||||
this.moreInputWanted = moreInputWanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
TaskMonitor interactiveTaskMonitor = plugin.getInteractiveTaskMonitor();
|
||||
JythonScript interactiveScript = plugin.getInteractiveScript();
|
||||
Program program = plugin.getCurrentProgram();
|
||||
InterpreterConsole console = plugin.getConsole();
|
||||
|
||||
// Setup transaction for the execution.
|
||||
try (Transaction tx = program != null ? program.openTransaction("Jython command") : null) {
|
||||
// Setup Ghidra state to be passed into interpreter
|
||||
interactiveTaskMonitor.clearCancelled();
|
||||
interactiveScript.setSourceFile(new ResourceFile(new File("jython")));
|
||||
PluginTool tool = plugin.getTool();
|
||||
interactiveScript.set(
|
||||
new GhidraState(tool, tool.getProject(), program, plugin.getProgramLocation(),
|
||||
plugin.getProgramSelection(), plugin.getProgramHighlight()),
|
||||
new ScriptControls(console, interactiveTaskMonitor));
|
||||
|
||||
// Execute the command
|
||||
moreInputWanted.set(false);
|
||||
moreInputWanted.set(plugin.getInterpreter().push(cmd, plugin.getInteractiveScript()));
|
||||
}
|
||||
catch (PyException pye) {
|
||||
String exceptionName = PyException.exceptionClassName(pye.type);
|
||||
if (exceptionName.equalsIgnoreCase("exceptions.SystemExit")) {
|
||||
plugin.reset();
|
||||
}
|
||||
else {
|
||||
console.getErrWriter()
|
||||
.println(
|
||||
"Suppressing exception: " + PyException.exceptionClassName(pye.type));
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
console.getErrWriter().println("Stack overflow!");
|
||||
}
|
||||
finally {
|
||||
interactiveScript.end(false); // end any transactions the script may have started
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Thread responsible for getting interactive lines of jython from the plugin.
|
||||
* This class also kicks off the execution of that line in a new {@link JythonPluginExecutionThread}.
|
||||
*/
|
||||
class JythonPluginInputThread extends Thread {
|
||||
|
||||
private static int generationCount = 0;
|
||||
|
||||
private final JythonPlugin plugin;
|
||||
private final AtomicBoolean moreInputWanted = new AtomicBoolean(false);
|
||||
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
|
||||
private final InputStream consoleStdin;
|
||||
private JythonPluginExecutionThread jythonExecutionThread;
|
||||
|
||||
/**
|
||||
* Creates a new jython input thread that gets a line of jython input from the given plugin.
|
||||
*
|
||||
* @param plugin The jython plugin to get input from.
|
||||
*/
|
||||
JythonPluginInputThread(JythonPlugin plugin) {
|
||||
super("Jython plugin input thread (generation " + ++generationCount + ")");
|
||||
this.plugin = plugin;
|
||||
this.consoleStdin = plugin.getConsole().getStdin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last jython plugin execution thread that ran.
|
||||
*
|
||||
* @return The last jython plugin execution thread that ran. Could be null if one never ran.
|
||||
*/
|
||||
JythonPluginExecutionThread getJythonPluginExecutionThread() {
|
||||
return jythonExecutionThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(consoleStdin))) {
|
||||
String line;
|
||||
while (!shutdownRequested.get() && (line = reader.readLine()) != null) {
|
||||
|
||||
// Execute the line in a new thread
|
||||
jythonExecutionThread =
|
||||
new JythonPluginExecutionThread(plugin, line, moreInputWanted);
|
||||
jythonExecutionThread.start();
|
||||
|
||||
try {
|
||||
// Wait for the execution to finish
|
||||
jythonExecutionThread.join();
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
// Hey we're back... a little earlier than expected, but there must be a reason.
|
||||
// So we'll go quietly.
|
||||
}
|
||||
|
||||
// Set the prompt appropriately
|
||||
plugin.getConsole()
|
||||
.setPrompt(
|
||||
moreInputWanted.get() ? plugin.getInterpreter().getSecondaryPrompt()
|
||||
: plugin.getInterpreter().getPrimaryPrompt());
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(JythonPluginInputThread.class,
|
||||
"Internal error reading commands from interpreter console. Please reset the interpreter.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the background thread's run() loop to exit.
|
||||
* <p>
|
||||
* Causes background thread's exit by closing the inputstream it is looping on.
|
||||
*/
|
||||
void shutdown() {
|
||||
try {
|
||||
shutdownRequested.set(true);
|
||||
consoleStdin.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// shouldn't happen, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.python.util.jython;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Launcher entry point for running Ghidra from within Jython.
|
||||
*/
|
||||
public class JythonRun implements GhidraLaunchable {
|
||||
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) {
|
||||
|
||||
// Initialize the application
|
||||
ApplicationConfiguration configuration = new HeadlessGhidraApplicationConfiguration();
|
||||
Application.initializeApplication(layout, configuration);
|
||||
|
||||
// Setup jython home directory
|
||||
try {
|
||||
JythonUtils.setupJythonHomeDir();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JythonRun.class, null, "Jython home directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Setup jython cache directory
|
||||
try {
|
||||
JythonUtils.setupJythonCacheDir(configuration.getTaskMonitor());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JythonRun.class, null, "Jython cache directory", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.showError(JythonRun.class, null, "Operation cancelled", e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Pass control to Jython
|
||||
jython.main(args);
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* A Jython version of a {@link GhidraScript}.
|
||||
*/
|
||||
public class JythonScript extends GhidraScript {
|
||||
|
||||
static final String JYTHON_INTERPRETER = "ghidra.jython.interpreter";
|
||||
|
||||
private AtomicBoolean interpreterRunning = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// Try to get the interpreter from an existing script state.
|
||||
GhidraJythonInterpreter interpreter =
|
||||
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
|
||||
|
||||
// Are we being called from an already running JythonScript with existing state?
|
||||
if (interpreter != null) {
|
||||
runInExistingEnvironment(interpreter);
|
||||
}
|
||||
else {
|
||||
runInNewEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runScript(String scriptName, GhidraState scriptState) throws Exception {
|
||||
GhidraJythonInterpreter interpreter =
|
||||
(GhidraJythonInterpreter) state.getEnvironmentVar(JYTHON_INTERPRETER);
|
||||
if (interpreter == null) {
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
if (interpreter == null) {
|
||||
throw new AssertException("Could not get Ghidra Jython interpreter!");
|
||||
}
|
||||
}
|
||||
ResourceFile scriptSource = GhidraScriptUtil.findScriptByName(scriptName);
|
||||
if (scriptSource != null) {
|
||||
GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSource);
|
||||
GhidraScript ghidraScript = provider.getScriptInstance(scriptSource, errorWriter);
|
||||
if (ghidraScript == null) {
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
updateStateFromVariables();
|
||||
}
|
||||
|
||||
try {
|
||||
interpreter.saveLocals();
|
||||
interpreter.clearLocals();
|
||||
if (ghidraScript instanceof JythonScript) {
|
||||
ghidraScript.set(scriptState);
|
||||
JythonScript jythonScript = (JythonScript) ghidraScript;
|
||||
interpreter.execFile(jythonScript.getSourceFile(), jythonScript);
|
||||
}
|
||||
else {
|
||||
ghidraScript.execute(scriptState, getControls());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
interpreter.restoreLocals();
|
||||
}
|
||||
|
||||
if (scriptState == state) {
|
||||
loadVariablesFromState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Script does not exist: " + scriptName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in an existing interpreter environment.
|
||||
*
|
||||
* @param interpreter The existing interpreter to execute from.
|
||||
*/
|
||||
private void runInExistingEnvironment(GhidraJythonInterpreter interpreter) {
|
||||
interpreter.execFile(sourceFile, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this script in a new interpreter environment and sticks the new interpreter
|
||||
* in the script state so it can be retrieved by scripts called from this script.
|
||||
*/
|
||||
private void runInNewEnvironment() {
|
||||
|
||||
// Create new interpreter and stick it in the script's state.
|
||||
final GhidraJythonInterpreter interpreter = GhidraJythonInterpreter.get();
|
||||
final PrintWriter stdout = getStdOut();
|
||||
final PrintWriter stderr = getStdErr();
|
||||
interpreter.setOut(stdout);
|
||||
interpreter.setErr(stderr);
|
||||
|
||||
// We stick the interpreter in the state so that if the script calls runScript, that
|
||||
// script will use the same interpreter. It is questionable whether or not we should do
|
||||
// this (the new script will get all of the old script's variables), but changing it now
|
||||
// could break people's scripts if they expect this behavior.
|
||||
state.addEnvironmentVar(JYTHON_INTERPRETER, interpreter);
|
||||
|
||||
// Execute the script in a new thread.
|
||||
JythonScriptExecutionThread executionThread =
|
||||
new JythonScriptExecutionThread(this, interpreter, interpreterRunning);
|
||||
interpreterRunning.set(true);
|
||||
executionThread.start();
|
||||
|
||||
// Wait for the script be finish running
|
||||
while (interpreterRunning.get() && !monitor.isCancelled()) {
|
||||
Thread.yield();
|
||||
sleep100millis();
|
||||
}
|
||||
if (interpreterRunning.get()) {
|
||||
// We've been canceled. Interrupt the interpreter.
|
||||
interpreter.interrupt(executionThread);
|
||||
}
|
||||
|
||||
// Script is done. Make sure the output displays.
|
||||
stderr.flush();
|
||||
stdout.flush();
|
||||
|
||||
// Cleanup the interpreter, and remove it from the state (once it's cleaned it cannot be
|
||||
// reused)
|
||||
interpreter.cleanup();
|
||||
state.removeEnvironmentVar(JYTHON_INTERPRETER);
|
||||
}
|
||||
|
||||
private PrintWriter getStdOut() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdOut();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.out, true);
|
||||
}
|
||||
|
||||
private PrintWriter getStdErr() {
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool != null) {
|
||||
ConsoleService console = tool.getService(ConsoleService.class);
|
||||
if (console != null) {
|
||||
return console.getStdErr();
|
||||
}
|
||||
}
|
||||
return new PrintWriter(System.err, true);
|
||||
}
|
||||
|
||||
private void sleep100millis() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Don't care; will probably be called again
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Jython";
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.python.core.PyException;
|
||||
|
||||
/**
|
||||
* Thread responsible for executing a jython script from a file.
|
||||
*/
|
||||
class JythonScriptExecutionThread extends Thread {
|
||||
|
||||
private JythonScript script;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
private AtomicBoolean interpreterRunning;
|
||||
|
||||
/**
|
||||
* Creates a new jython script execution thread that executes the given jython script.
|
||||
*
|
||||
* @param script The jython script to execute.
|
||||
* @param interpreter The jython interpreter to use for execution.
|
||||
* @param interpreterRunning Gets set to indicate whether or not the interpreter is still running the script.
|
||||
*/
|
||||
JythonScriptExecutionThread(JythonScript script, GhidraJythonInterpreter interpreter,
|
||||
AtomicBoolean interpreterRunning) {
|
||||
super("Jython script execution thread");
|
||||
|
||||
this.script = script;
|
||||
this.interpreter = interpreter;
|
||||
this.interpreterRunning = interpreterRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
interpreter.execFile(script.getSourceFile(), script);
|
||||
}
|
||||
catch (PyException pye) {
|
||||
if (PyException.exceptionClassName(pye.type).equalsIgnoreCase(
|
||||
"exceptions.SystemExit")) {
|
||||
interpreter.printErr("SystemExit");
|
||||
}
|
||||
else {
|
||||
pye.printStackTrace(); // this prints to the interpreter error stream.
|
||||
}
|
||||
}
|
||||
catch (StackOverflowError soe) {
|
||||
interpreter.printErr("Stack overflow!");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
interpreter.printErr(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
interpreterRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.*;
|
||||
|
||||
/**
|
||||
* A {@link GhidraScriptProvider} used to run Jython scripts
|
||||
*/
|
||||
public class JythonScriptProvider extends AbstractPythonScriptProvider {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Jython";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuntimeEnvironmentName() {
|
||||
return "Jython";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
||||
throws GhidraScriptLoadException {
|
||||
|
||||
try {
|
||||
Class<?> clazz = Class.forName(JythonScript.class.getName());
|
||||
GhidraScript script = (GhidraScript) clazz.getConstructor().newInstance();
|
||||
script.setSourceFile(sourceFile);
|
||||
return script;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new GhidraScriptLoadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Python utility method class.
|
||||
*/
|
||||
public class JythonUtils {
|
||||
|
||||
public static final String JYTHON_NAME = "jython-2.7.4";
|
||||
public static final String JYTHON_CACHEDIR = "jython_cachedir";
|
||||
public static final String JYTHON_SRC = "jython-src";
|
||||
|
||||
/**
|
||||
* Sets up the jython home directory. This is the directory that has the "Lib" directory in it.
|
||||
*
|
||||
* @return The jython home directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the home directory.
|
||||
*/
|
||||
public static File setupJythonHomeDir() throws IOException {
|
||||
|
||||
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File jythonHomeDir =
|
||||
Application.getModuleDataSubDirectory(jythonModuleDir.getName(), JYTHON_NAME)
|
||||
.getFile(false);
|
||||
|
||||
if (!jythonHomeDir.exists()) {
|
||||
throw new IOException("Failed to find the jython home directory at: " + jythonHomeDir);
|
||||
}
|
||||
|
||||
System.setProperty("jython.home", jythonHomeDir.getAbsolutePath());
|
||||
|
||||
return jythonHomeDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the jython cache directory. This is a temporary space that jython source files
|
||||
* get compiled to and cached. It should NOT be in the Ghidra installation directory, because
|
||||
* some installations will not have the appropriate directory permissions to create new files in.
|
||||
*
|
||||
* @param monitor A monitor to use during the cache directory setup.
|
||||
* @return The jython cache directory.
|
||||
* @throws IOException If there was a disk-related problem setting up the cache directory.
|
||||
* @throws CancelledException If the user cancelled the setup.
|
||||
*/
|
||||
public static File setupJythonCacheDir(TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
|
||||
File devDir = new File(Application.getUserSettingsDirectory(), "dev");
|
||||
File cacheDir = new File(devDir, JYTHON_CACHEDIR);
|
||||
if (!FileUtilities.mkdirs(cacheDir)) {
|
||||
throw new IOException("Failed to create the jython cache directory at: " + cacheDir);
|
||||
}
|
||||
|
||||
File jythonSrcDestDir = new File(cacheDir, JYTHON_SRC);
|
||||
if (!FileUtilities.createDir(jythonSrcDestDir)) {
|
||||
throw new IOException(
|
||||
"Failed to create the " + JYTHON_SRC + " directory at: " + jythonSrcDestDir);
|
||||
}
|
||||
|
||||
File jythonModuleDir = Application.getMyModuleRootDirectory().getFile(false);
|
||||
File jythonSrcDir = new File(jythonModuleDir, JYTHON_SRC);
|
||||
if (!jythonSrcDir.exists()) {
|
||||
try {
|
||||
jythonSrcDir = Application.getModuleDataSubDirectory(jythonModuleDir.getName(),
|
||||
JYTHON_SRC).getFile(false);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new IOException("Failed to find the module's " + JYTHON_SRC + " directory");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
FileUtilities.copyDir(jythonSrcDir, jythonSrcDestDir, f -> f.getName().endsWith(".py"),
|
||||
monitor);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IOException(
|
||||
"Failed to copy " + JYTHON_SRC + " files to: " + jythonSrcDestDir);
|
||||
}
|
||||
|
||||
System.setProperty("python.cachedir.skip", "false");
|
||||
System.setProperty("python.cachedir", cacheDir.getAbsolutePath());
|
||||
System.setProperty("python.path", jythonSrcDestDir.getAbsolutePath());
|
||||
|
||||
return cacheDir;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PyDevUtils {
|
||||
|
||||
public static final int PYDEV_REMOTE_DEBUGGER_PORT = 5678;
|
||||
|
||||
/**
|
||||
* Gets The PyDev source directory.
|
||||
*
|
||||
* @return The PyDev source directory, or null if it not known.
|
||||
*/
|
||||
public static File getPyDevSrcDir() {
|
||||
String property = System.getProperty("eclipse.pysrc.dir");
|
||||
return StringUtils.isNotBlank(property) ? new File(property) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent instantiation of utility class.
|
||||
*/
|
||||
private PyDevUtils() {
|
||||
}
|
||||
}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.*;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
/**
|
||||
* Tests for the Ghidra Python Interpreter's code completion functionality.
|
||||
*/
|
||||
public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private String simpleTestProgram = """
|
||||
my_int = 32
|
||||
my_bool = True
|
||||
my_string = 'this is a string'
|
||||
my_list = ["a", 2, 5.3, my_string]
|
||||
my_tuple = (1, 2, 3)
|
||||
my_dictionary = {"key1": "1", "key2": 2, "key3": my_list}
|
||||
mY_None = None
|
||||
i = 5
|
||||
|
||||
def factorial(n):
|
||||
return 1 if n == 0 else n * factorial(n-1)
|
||||
def error_function():
|
||||
raise IOError("An IO error occurred!")
|
||||
|
||||
class Employee:
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
def getId(self):
|
||||
return self.id
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
employee = Employee(42, "Bob")
|
||||
""".stripIndent();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempScriptFolder = new TemporaryFolder();
|
||||
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GhidraScriptUtil.acquireBundleHostReference();
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
executeJythonProgram(simpleTestProgram);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
interpreter.cleanup();
|
||||
GhidraScriptUtil.releaseBundleHostReference();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicCodeCompletion() {
|
||||
// test the "insertion" field
|
||||
// it should be equal to the full name of a variable we want to complete
|
||||
|
||||
List<String> completions = List.of("my_bool", "my_dictionary", "my_int", "my_list",
|
||||
"mY_None", "my_string", "my_tuple");
|
||||
assertCompletionsInclude("My", completions);
|
||||
assertCompletionsInclude("employee.Get", List.of("getId", "getName"));
|
||||
assertCompletionsInclude("('noise', (1 + fact", List.of("factorial"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharsToRemoveField() {
|
||||
// 'charsToRemove' field should be equal to the length of
|
||||
// a part of variable/function/method name we are trying to complete here.
|
||||
// This allows us to correctly put a completion in cases when we really
|
||||
// just want to replace a piece of text (i.e. "CURRENTAddress" => "currentAddress")
|
||||
// rather than simply 'complete' it.
|
||||
|
||||
assertCharsToRemoveEqualsTo("my_int", "my_int".length());
|
||||
assertCharsToRemoveEqualsTo("employee.get", "get".length());
|
||||
assertCharsToRemoveEqualsTo("('noise', (1 + fact", "fact".length());
|
||||
|
||||
assertCharsToRemoveEqualsTo("employee.", 0);
|
||||
assertCharsToRemoveEqualsTo("employee.getId(", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompletionsFromArbitraryCaretPositions() {
|
||||
// '<caret>' designates the position of the caret
|
||||
|
||||
String testCase;
|
||||
testCase = "MY_TUP<caret>";
|
||||
assertCompletionsInclude(testCase, List.of("my_tuple"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "my_tup".length());
|
||||
|
||||
testCase = "employee.Get<caret>; (4, 2+2)";
|
||||
assertCompletionsInclude(testCase, List.of("getId", "getName"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "Get".length());
|
||||
|
||||
testCase = "bar = e<caret> if 2+2==4 else 'baz'";
|
||||
assertCompletionsInclude(testCase, List.of("error_function", "employee"));
|
||||
assertCharsToRemoveEqualsTo(testCase, "e".length());
|
||||
}
|
||||
|
||||
private void assertCompletionsInclude(String command, Collection<String> expectedCompletions) {
|
||||
Set<String> completions = getCodeCompletions(command)
|
||||
.stream()
|
||||
.map(c -> c.getInsertion())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var missing = new HashSet<String>(expectedCompletions);
|
||||
missing.removeAll(completions);
|
||||
if (!missing.isEmpty()) {
|
||||
Assert.fail("Could't find these completions: " + missing);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertCharsToRemoveEqualsTo(String command, int expectedCharsToRemove) {
|
||||
for (CodeCompletion comp : getCodeCompletions(command)) {
|
||||
assertEquals(String.format("%s; field 'charsToRemove' ", comp), expectedCharsToRemove,
|
||||
comp.getCharsToRemove());
|
||||
}
|
||||
}
|
||||
|
||||
private List<CodeCompletion> getCodeCompletions(String command) {
|
||||
// extract the caret position and generate completions relative to that place
|
||||
int caretPos = command.indexOf("<caret>");
|
||||
command.replace("<caret>", "");
|
||||
if (caretPos == -1) {
|
||||
caretPos = command.length();
|
||||
}
|
||||
|
||||
return interpreter.getCommandCompletions(command, false, caretPos);
|
||||
}
|
||||
|
||||
private void executeJythonProgram(String code) {
|
||||
try {
|
||||
File tempFile = tempScriptFolder.newFile();
|
||||
FileUtils.writeStringToFile(tempFile, code, Charset.defaultCharset());
|
||||
interpreter.execFile(new ResourceFile(tempFile), null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
/**
|
||||
* Tests the Ghidra python interpreter's functionality.
|
||||
*/
|
||||
public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private ByteArrayOutputStream out;
|
||||
private GhidraJythonInterpreter interpreter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
out = new ByteArrayOutputStream();
|
||||
GhidraScriptUtil.acquireBundleHostReference();
|
||||
interpreter = GhidraJythonInterpreter.get();
|
||||
interpreter.setOut(out);
|
||||
interpreter.setErr(out);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
out.reset();
|
||||
interpreter.cleanup();
|
||||
GhidraScriptUtil.releaseBundleHostReference();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "push" method is working by executing a simple line of python.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonPush() {
|
||||
final String str = "hi";
|
||||
interpreter.push("print \"" + str + "\"", null);
|
||||
assertEquals(out.toString().trim(), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the interpreter's "execFile" method is working by executing a simple file of python.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonExecFile() {
|
||||
interpreter.execFile(new ResourceFile("ghidra_scripts/python_basics.py"), null);
|
||||
assertTrue(out.toString().contains("Snoopy"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that our sitecustomize.py modules gets loaded by testing the custom help function
|
||||
* that we install from there.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonSiteCustomize() {
|
||||
interpreter.push("help", null);
|
||||
assertTrue(out.toString().contains("Press 'F1'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that cleaning the interpreter invalidates it.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonCleanupInvalidation() {
|
||||
interpreter.cleanup();
|
||||
|
||||
try {
|
||||
interpreter.push("pass", null);
|
||||
fail("Push still worked after interpreter cleanup.");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// If everything worked, we should end up here.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
|
||||
/**
|
||||
* Tests the Python Plugin functionality.
|
||||
*/
|
||||
public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private JythonPlugin plugin;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
GhidraScriptUtil.acquireBundleHostReference();
|
||||
tool.addPlugin(JythonPlugin.class.getName());
|
||||
plugin = env.getPlugin(JythonPlugin.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.releaseBundleHostReference();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that issuing a reset from the plugin resets the interpreter.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonPluginReset() {
|
||||
GhidraJythonInterpreter origInterpreter = plugin.getInterpreter();
|
||||
plugin.reset();
|
||||
GhidraJythonInterpreter newInterpreter = plugin.getInterpreter();
|
||||
assertNotSame(origInterpreter, newInterpreter);
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.ScriptInfo;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
GhidraScriptUtil.acquireBundleHostReference();
|
||||
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
if (Files.notExists(userScriptDir)) {
|
||||
Files.createDirectories(userScriptDir);
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.releaseBundleHostReference();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetailedJythonScript() {
|
||||
String descLine1 = "This script exists to check that the info on";
|
||||
String descLine2 = "a script that has extensive documentation is";
|
||||
String descLine3 = "properly parsed and represented.";
|
||||
String author = "Fake Name";
|
||||
String categoryTop = "Test";
|
||||
String categoryBottom = "ScriptInfo";
|
||||
String keybinding = "ctrl shift COMMA";
|
||||
String menupath = "File.Run.Detailed Script";
|
||||
String importPackage = "detailStuff";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + descLine1,
|
||||
"#" + descLine2,
|
||||
"#" + descLine3,
|
||||
"#@author " + author,
|
||||
"#@category " + categoryTop + "." + categoryBottom,
|
||||
"#@keybinding " + keybinding,
|
||||
"#@menupath " + menupath,
|
||||
"#@importpackage " + importPackage,
|
||||
"print('for a blank class, it sure is well documented!')");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
|
||||
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
|
||||
assertEquals(expectedDescription, info.getDescription());
|
||||
|
||||
assertEquals(author, info.getAuthor());
|
||||
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
|
||||
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
|
||||
assertEquals(importPackage, info.getImportPackage());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(2, actualCategory.length);
|
||||
assertEquals(categoryTop, actualCategory[0]);
|
||||
assertEquals(categoryBottom, actualCategory[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithBlockCommentAndCertifyHeader() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"## ###",
|
||||
"# IP: GHIDRA",
|
||||
"# ",
|
||||
"# Some license text...",
|
||||
"# you may not use this file except in compliance with the License.",
|
||||
"# ",
|
||||
"# blah blah blah",
|
||||
"##",
|
||||
"",
|
||||
"'''",
|
||||
"This is a test block comment. It will be ignored.",
|
||||
"@category NotTheRealCategory",
|
||||
"'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithoutBlockComment() {
|
||||
String description = "Script without a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJythonScriptWithSingleLineBlockComment() {
|
||||
String description = "Script with a block comment at the top.";
|
||||
String category = "Test";
|
||||
ResourceFile scriptFile = null;
|
||||
|
||||
try {
|
||||
//@formatter:off
|
||||
scriptFile = createTempPyScriptFileWithLines(
|
||||
"'''This is a test block comment. It will be ignored.'''",
|
||||
"#" + description,
|
||||
"#@category " + category,
|
||||
"print 'hello!'");
|
||||
//@formatter:on
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("couldn't create a test script: " + e.getMessage());
|
||||
}
|
||||
|
||||
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||
assertEquals(description + " \n", info.getDescription());
|
||||
|
||||
String[] actualCategory = info.getCategory();
|
||||
assertEquals(1, actualCategory.length);
|
||||
assertEquals(category, actualCategory[0]);
|
||||
}
|
||||
|
||||
private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException {
|
||||
File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||
File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir);
|
||||
tempFile.deleteOnExit();
|
||||
ResourceFile tempResourceFile = new ResourceFile(tempFile);
|
||||
|
||||
PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream());
|
||||
for (String line : lines) {
|
||||
writer.println(line);
|
||||
}
|
||||
writer.close();
|
||||
|
||||
return tempResourceFile;
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.jython;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Tests the Python script functionality.
|
||||
*/
|
||||
public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private ConsoleService console;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
GhidraScriptUtil.acquireBundleHostReference();
|
||||
tool.addPlugin(ConsolePlugin.class.getName());
|
||||
console = tool.getService(ConsoleService.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
GhidraScriptUtil.releaseBundleHostReference();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Jython scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonScript() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
String output = runPythonScript(Application.getModuleFile("Jython", script));
|
||||
assertTrue(output.contains("Snoopy"));
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find jython script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Jython scripts are running correctly.
|
||||
*
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
@Test
|
||||
public void testJythonInterpreterGoneFromState() throws Exception {
|
||||
String script = "ghidra_scripts/python_basics.py";
|
||||
try {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
runPythonScript(Application.getModuleFile("Jython", script), state);
|
||||
assertTrue(state.getEnvironmentVar(JythonScript.JYTHON_INTERPRETER) == null);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
fail("Could not find python script: " + script);
|
||||
}
|
||||
catch (Exception e) {
|
||||
fail("Exception occurred trying to run script: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile) throws Exception {
|
||||
GhidraState state =
|
||||
new GhidraState(env.getTool(), env.getProject(), null, null, null, null);
|
||||
return runPythonScript(scriptFile, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given Python script with the given initial state.
|
||||
*
|
||||
* @param scriptFile The Python script to run.
|
||||
* @param state The initial state of the script.
|
||||
* @return The console output of the script.
|
||||
* @throws Exception If an exception occurred while trying to run the script.
|
||||
*/
|
||||
private String runPythonScript(ResourceFile scriptFile, GhidraState state) throws Exception {
|
||||
|
||||
runSwing(() -> console.clearMessages());
|
||||
|
||||
JythonScriptProvider scriptProvider = new JythonScriptProvider();
|
||||
PrintWriter writer = new PrintWriter(new ByteArrayOutputStream());
|
||||
JythonScript script = (JythonScript) scriptProvider.getScriptInstance(scriptFile, writer);
|
||||
script.set(state, new ScriptControls(writer, writer, TaskMonitor.DUMMY));
|
||||
script.run();
|
||||
|
||||
waitForSwing();
|
||||
|
||||
AtomicReference<String> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
String text = console.getText(0, console.getTextLength());
|
||||
ref.set(text);
|
||||
});
|
||||
|
||||
String text = ref.get();
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user