ghidra/Ghidra/Features/Python/python-src/ghidradoc.py

176 lines
7.8 KiB
Python

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