Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor python script/gradle and (hopefully) add Mac M1 support #48

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -73,4 +73,14 @@ if (ghidraInstallDir) {
else {
throw new GradleException("GHIDRA_INSTALL_DIR is not defined!")
}
//----------------------END "DO NOT MODIFY" SECTION-------------------------------
//----------------------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')
3 changes: 3 additions & 0 deletions os/mac_arm_64/README.txt
Original file line number Diff line number Diff line change
@@ -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.
95 changes: 86 additions & 9 deletions util/configure_jep_native_binaries.py → util/buildsupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,33 @@
# 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

JEP_PY_FOLDER_NAME = "jep"
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<version>[\d\.]+)_PUBLIC_\d+_(?P<name>.+)\.zip")

logger = logging.getLogger(__name__)

Expand All @@ -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")

Expand Down Expand Up @@ -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))