diff --git a/build.gradle b/build.gradle index 59b952f..d97009c 100644 --- a/build.gradle +++ b/build.gradle @@ -41,16 +41,16 @@ task installJep(type: Exec) { // we need to copy the Jep native binaries built in installJep to our extension directory; we use a small // utility script written in Python -task copyJepNativeBinaries(type: Exec) { +task preBuild(type: Exec) { dependsOn installJep workingDir "${projectDir}" - commandLine pythonBin, "util${File.separator}configure_jep_native_binaries.py" + commandLine pythonBin, "util${File.separator}buildsupport.py", 'prebuild' } -// make all tasks not matching copyJepNativeBinaries or installJep depend on copyJepNativeBinaries; mostly +// make all tasks not matching preBuild or installJep depend on preBuild; mostly // used to ensure our tasks run before Ghidra buildExtension task -tasks.matching { it.name != 'copyJepNativeBinaries' && it.name != 'installJep' }.all { Task task -> - task.dependsOn copyJepNativeBinaries +tasks.matching { it.name != 'preBuild' && it.name != 'installJep' }.all { Task task -> + task.dependsOn preBuild } // from here we use the standard Gradle build provided by Ghidra framework @@ -73,4 +73,14 @@ if (ghidraInstallDir) { else { throw new GradleException("GHIDRA_INSTALL_DIR is not defined!") } -//----------------------END "DO NOT MODIFY" SECTION------------------------------- \ No newline at end of file +//----------------------END "DO NOT MODIFY" SECTION------------------------------- + +// post-build scripted steps +// automate install to ghidra and rename jep library for Mac M1 +task postBuild(type: Exec) { + dependsOn buildExtension + workingDir "${projectDir}" + commandLine pythonBin, "util${File.separator}buildsupport.py", 'postbuild' +} + +buildExtension.finalizedBy('postBuild') \ No newline at end of file diff --git a/os/mac_arm_64/README.txt b/os/mac_arm_64/README.txt new file mode 100644 index 0000000..a2f926b --- /dev/null +++ b/os/mac_arm_64/README.txt @@ -0,0 +1,3 @@ +The "os/mac_arm_64" directory is intended to hold macOS native binaries +which this module is dependent upon. This directory may be eliminated for a specific +module if native binaries are not provided for the corresponding platform. diff --git a/util/configure_jep_native_binaries.py b/util/buildsupport.py similarity index 51% rename from util/configure_jep_native_binaries.py rename to util/buildsupport.py index d36ca85..d6c1310 100644 --- a/util/configure_jep_native_binaries.py +++ b/util/buildsupport.py @@ -6,12 +6,17 @@ # 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. +import os import subprocess import argparse import logging import shutil import glob import sys +import platform +import zipfile +import tempfile +import re from pathlib import Path @@ -19,12 +24,15 @@ JEP_OS_LIB_NAME_WINDOWS = "jep.dll" JEP_OS_LIB_NAME_LINUX = "libjep.so" JEP_OS_LIB_NAME_DARWIN = "jep.cpython-%d%d-darwin.so" % sys.version_info[:2] +JEP_OS_LIB_NAME_DARWIN_M1 = "libjep.jnilib" GHIDRA_JAVA_LIB_PATH = "lib" GHIDRA_OS_LIB_PATH_WINDOWS = "os/win_x86_64/jep.dll" GHIDRA_OS_LIB_PATH_LINUX = "os/linux_x86_64/libjep.so" GHIDRA_OS_LIB_PATH_DARWIN = "os/mac_x86_64/libjep.so" +GHIDRA_OS_LIB_PATH_DARWIN_M1 = "os/mac_arm_64/libjep.jnilib" +RE_DIST_NAME = re.compile(r"ghidra_(?P[\d\.]+)_PUBLIC_\d+_(?P.+)\.zip") logger = logging.getLogger(__name__) @@ -49,17 +57,53 @@ def find_jep_dir(): return jep_dir return Path() +def rename_jep_jnilib(zip_path): + """renames libjep.jnilib to jep.dll for Mac M1""" + # creates a temporary directory, extracts contents of distro to it + # renames os/mac_arm_64/libjep.jnilib to jep.dll + # overwrites distro with new zipfile + # inefficient because renaming files in zipfiles is annoying + logger.debug("Renaming jep binaries in distro...") + name = os.path.basename(zip_path) + match = RE_DIST_NAME.search(name) + if not match: + logger.error(f"Ghidrathon archive not found! {name}") + return -1 + ghidra_version = match.group('version') + ghidrathon_name = match.group('name') + with tempfile.TemporaryDirectory() as tmpdirname: + tmpdir = Path(tmpdirname) + with zipfile.ZipFile(zip_path, mode='r') as distro: + distro.extractall(tmpdirname) + os.remove(zip_path) + os.rename( + f"{tmpdirname}/{ghidrathon_name}/os/mac_arm_64/libjep.jnilib", + f"{tmpdirname}/{ghidrathon_name}/os/mac_arm_64/jep.dll" + ) + logger.debug("repacking distro...") + with zipfile.ZipFile(zip_path, mode='w') as newdistro: + for path in tmpdir.rglob(f'*'): + logger.debug(str(path.relative_to(tmpdir))) + newdistro.write(path, arcname=path.relative_to(tmpdir)) + logger.info("renamed jep binaries.") + return 0 -def main(args): - """ """ + +def pre_build(args): + """Locate Jep module directory and copy necessary files to Ghidrathon extension directories.""" if args.debug: logger.setLevel(logging.DEBUG) if sys.platform in ("darwin",): logger.debug("Detected macOS") - - os_lib_name = JEP_OS_LIB_NAME_DARWIN - os_lib_path = Path(GHIDRA_OS_LIB_PATH_DARWIN) + arch = platform.machine() + if arch == "arm64": + logger.debug("Detected M1") + os_lib_name = JEP_OS_LIB_NAME_DARWIN_M1 + os_lib_path = Path(GHIDRA_OS_LIB_PATH_DARWIN_M1) + else: + os_lib_name = JEP_OS_LIB_NAME_DARWIN + os_lib_path = Path(GHIDRA_OS_LIB_PATH_DARWIN) elif sys.platform in ("win32", "cygwin"): logger.debug("Detected Windows OS") @@ -115,13 +159,46 @@ def main(args): return 0 +def post_build(args): + """do post-build actions (install to ghidra, etc.)""" + logger.info("Running post-build script") + if args.debug: + logger.setLevel(logging.DEBUG) + + distro_path = max(glob.glob("dist/*.zip"), key=os.path.getctime) + + if sys.platform == 'darwin' and platform.machine() == 'arm64': + logger.info("Detected Mac M1") + # do file rewrites for Mac M1 + # get most recently built distribution + rename_jep_jnilib(distro_path) + + if args.ghidra_dir: + # extract distro to Ghidra/extensions + extensions_dir = Path(args.ghidra_dir) / 'Ghidra' / 'Extensions' + with zipfile.ZipFile(distro_path, mode='r') as distro: + distro.extractall(extensions_dir) + + if __name__ == "__main__": """ """ parser = argparse.ArgumentParser( - description="Locate Jep module directory and copy necessary files to Ghidrathon extension directories." + description="pre-build and post-build support scripts" ) - - parser.add_argument("-p", "--path", type=str, help="Full path to Jep Python module directory") parser.add_argument("-d", "--debug", action="store_true", help="Show debug messages") - sys.exit(main(parser.parse_args())) + subparsers = parser.add_subparsers(dest="script", help='sub-command help') + + pre_parser = subparsers.add_parser('prebuild', help="run prebuild script (configure jep native binaries)") + pre_parser.add_argument("-p", "--path", type=str, help="Full path to Jep Python module directory") + + post_parser = subparsers.add_parser('postbuild', help="run postbuild script") + post_parser.add_argument("--ghidra-install-dir", type=str, dest="ghidra_dir", default=None, + help="ghidra install directory (automatically install Ghidrathon extension if provided)") + + args = parser.parse_args() + + if args.script == 'prebuild': + sys.exit(pre_build(args)) + elif args.script == 'postbuild': + sys.exit(post_build(args))