mirror of
https://github.com/cea-sec/miasm
synced 2026-08-17 10:26:03 -04:00
343 lines
12 KiB
Python
343 lines
12 KiB
Python
from setuptools import Extension, setup
|
|
from setuptools.command.build_ext import build_ext
|
|
from setuptools.command.build_py import build_py
|
|
from setuptools.command.sdist import sdist
|
|
|
|
import atexit
|
|
import fnmatch
|
|
import os
|
|
import platform
|
|
import re
|
|
import subprocess
|
|
import sysconfig
|
|
import tempfile
|
|
from shutil import copy2, copyfile, rmtree, which
|
|
|
|
|
|
is_win = platform.system() == "Windows"
|
|
is_mac = platform.system() == "Darwin"
|
|
is_64bit = platform.architecture()[0] == "64bit"
|
|
require_jit = os.environ.get("MIASM_REQUIRE_JIT") == "1"
|
|
if is_win:
|
|
import winreg
|
|
|
|
BUILD_WARNINGS = []
|
|
|
|
|
|
def set_extension_compile_args(extension):
|
|
rel_lib_path = extension.name.replace(".", "/")
|
|
abs_lib_path = os.path.join(sysconfig.get_path("platlib"), rel_lib_path)
|
|
lib_name = abs_lib_path + ".so"
|
|
extension.extra_link_args = ["-Wl,-install_name," + lib_name]
|
|
|
|
|
|
def win_get_llvm_reg():
|
|
REG_PATH = "SOFTWARE\\LLVM\\LLVM"
|
|
try:
|
|
return winreg.OpenKey(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
REG_PATH,
|
|
0,
|
|
winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
|
|
)
|
|
except FileNotFoundError:
|
|
pass
|
|
return winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, REG_PATH, 0, winreg.KEY_READ)
|
|
|
|
|
|
def win_find_clang_path():
|
|
try:
|
|
with win_get_llvm_reg() as rkey:
|
|
return winreg.QueryValueEx(rkey, None)[0]
|
|
except FileNotFoundError:
|
|
# Visual Studio ships with an optional Clang distribution; detect that
|
|
# when the standalone LLVM registry key is not present.
|
|
clang_cl = which("clang-cl")
|
|
if clang_cl is None:
|
|
return None
|
|
return os.path.abspath(os.path.join(os.path.dirname(clang_cl), "..", ".."))
|
|
|
|
|
|
def win_get_clang_version(clang_path):
|
|
try:
|
|
clang = os.path.join(clang_path, "bin", "clang.exe")
|
|
stdout = subprocess.check_output('"{}" --version'.format(clang))
|
|
version = stdout.splitlines(False)[0].decode()
|
|
match = re.search(r"version (\d+\.\d+\.\d+)", version)
|
|
if match is None:
|
|
return None
|
|
return [int(part) for part in match.group(1).split(".")]
|
|
except FileNotFoundError:
|
|
return None
|
|
|
|
|
|
def win_use_clang():
|
|
# To force setuptools to use clang-cl, copy the LLVM tools into a
|
|
# temporary directory as cl.exe/link.exe and put that directory first in
|
|
# PATH. Using the build directory would avoid a tempdir, but setuptools
|
|
# does not expose a reliable build path before build_ext starts.
|
|
clang_path = win_find_clang_path()
|
|
if clang_path is None:
|
|
return False
|
|
clang_version = win_get_clang_version(clang_path)
|
|
if clang_version is None:
|
|
return False
|
|
|
|
tmpdir = tempfile.mkdtemp(prefix="llvm")
|
|
try:
|
|
copyfile(os.path.join(clang_path, "bin", "clang-cl.exe"), os.path.join(tmpdir, "cl.exe"))
|
|
# When forcing clang, put lld-link.exe first as link.exe so setuptools
|
|
# uses the LLVM-compatible linker. LLVM >= 14.0.0 is required because
|
|
# earlier versions do not support MSVC's /LTCG flag and fail during
|
|
# linking.
|
|
if clang_version[0] < 14:
|
|
rmtree(tmpdir)
|
|
return False
|
|
copyfile(os.path.join(clang_path, "bin", "lld-link.exe"), os.path.join(tmpdir, "link.exe"))
|
|
except FileNotFoundError:
|
|
rmtree(tmpdir)
|
|
return False
|
|
|
|
# Add the temporary directory at the front of PATH and clean it up when the
|
|
# build process exits.
|
|
os.environ["PATH"] = "%s;%s" % (tmpdir, os.environ["PATH"])
|
|
atexit.register(lambda dir_: rmtree(dir_), tmpdir)
|
|
print(
|
|
"Found Clang {}.{}.{}: {}".format(
|
|
clang_version[0], clang_version[1], clang_version[2], clang_path
|
|
)
|
|
)
|
|
return True
|
|
|
|
|
|
def make_ext_modules(optional):
|
|
vm_common = [
|
|
"miasm/jitter/vm_mngr.c",
|
|
"miasm/jitter/vm_mngr_py.c",
|
|
"miasm/jitter/bn.c",
|
|
]
|
|
core_common = [
|
|
"miasm/jitter/JitCore.c",
|
|
*vm_common,
|
|
"miasm/jitter/op_semantics.c",
|
|
]
|
|
|
|
return [
|
|
Extension("miasm.jitter.VmMngr", vm_common, optional=optional),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_x86",
|
|
[*core_common, "miasm/jitter/arch/JitCore_x86.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_arm",
|
|
[*core_common, "miasm/jitter/arch/JitCore_arm.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_aarch64",
|
|
[*core_common, "miasm/jitter/arch/JitCore_aarch64.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_msp430",
|
|
[*core_common, "miasm/jitter/arch/JitCore_msp430.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_mep",
|
|
[
|
|
"miasm/jitter/JitCore.c",
|
|
*vm_common,
|
|
"miasm/jitter/arch/JitCore_mep.c",
|
|
],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_mips32",
|
|
[*core_common, "miasm/jitter/arch/JitCore_mips32.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_ppc32",
|
|
[*core_common, "miasm/jitter/arch/JitCore_ppc32.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.arch.JitCore_m68k",
|
|
[*core_common, "miasm/jitter/arch/JitCore_m68k.c"],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.Jitllvm",
|
|
[
|
|
"miasm/jitter/Jitllvm.c",
|
|
"miasm/jitter/bn.c",
|
|
"miasm/runtime/udivmodti4.c",
|
|
"miasm/runtime/divti3.c",
|
|
"miasm/runtime/udivti3.c",
|
|
],
|
|
optional=optional,
|
|
),
|
|
Extension(
|
|
"miasm.jitter.Jitgcc",
|
|
["miasm/jitter/Jitgcc.c", "miasm/jitter/bn.c"],
|
|
optional=optional,
|
|
),
|
|
]
|
|
|
|
|
|
def configured_ext_modules():
|
|
build_extensions = True
|
|
win_force_clang = False
|
|
|
|
if is_win:
|
|
if is_64bit or which("cl") is None:
|
|
# 64-bit builds require clang for uint128_t support. In 32-bit mode
|
|
# the ABI does not use uint128_t, so MSVC is fine; still try clang
|
|
# there if cl.exe is missing from PATH.
|
|
win_force_clang = win_use_clang()
|
|
if is_64bit and not win_force_clang:
|
|
BUILD_WARNINGS.append(
|
|
"Could not find a suitable Clang/LLVM installation. "
|
|
"You can download LLVM from https://releases.llvm.org"
|
|
)
|
|
BUILD_WARNINGS.append(
|
|
"Alternatively you can select the 'C++ Clang-cl build tools' "
|
|
"in the Visual Studio Installer"
|
|
)
|
|
build_extensions = False
|
|
|
|
cl = which("cl")
|
|
link = which("link")
|
|
if cl is None or link is None:
|
|
BUILD_WARNINGS.append(
|
|
"Could not find cl.exe and/or link.exe in the PATH, try building "
|
|
"miasm from a Visual Studio command prompt"
|
|
)
|
|
BUILD_WARNINGS.append("More information at: https://wiki.python.org/moin/WindowsCompilers")
|
|
build_extensions = False
|
|
else:
|
|
print("Found cl.exe: {}".format(cl))
|
|
print("Found link.exe: {}".format(link))
|
|
|
|
if not build_extensions:
|
|
message = "miasm jit extensions will not be compiled, details:"
|
|
if require_jit:
|
|
print("ERROR: " + message)
|
|
else:
|
|
print("WARNING: " + message)
|
|
for warning in BUILD_WARNINGS:
|
|
print(" " + warning)
|
|
if require_jit:
|
|
raise RuntimeError("Unable to build miasm native extensions")
|
|
return []
|
|
|
|
ext_modules = make_ext_modules(optional=not require_jit)
|
|
|
|
if is_win:
|
|
# Force setuptools to use the compiler/linker already selected in PATH.
|
|
# https://docs.python.org/3/distutils/apiref.html#module-distutils.msvccompiler
|
|
os.environ["MSSdk"] = "1"
|
|
os.environ["DISTUTILS_USE_SDK"] = "1"
|
|
extra_compile_args = ["-D_CRT_SECURE_NO_WARNINGS"]
|
|
if win_force_clang:
|
|
march = "-m64" if is_64bit else "-m32"
|
|
extra_compile_args += [
|
|
march,
|
|
"-Wno-unused-command-line-argument",
|
|
"-Wno-visibility",
|
|
"-Wno-dll-attribute-on-redeclaration",
|
|
"-Wno-tautological-compare",
|
|
"-Wno-unused-but-set-variable",
|
|
]
|
|
for extension in ext_modules:
|
|
extension.extra_compile_args = extra_compile_args
|
|
elif is_mac:
|
|
for extension in ext_modules:
|
|
set_extension_compile_args(extension)
|
|
cfg_vars = sysconfig.get_config_vars()
|
|
ldshared = cfg_vars.get("LDSHARED")
|
|
if ldshared:
|
|
cfg_vars["LDSHARED"] = ldshared.replace("-bundle", "-dynamiclib")
|
|
|
|
return ext_modules
|
|
|
|
|
|
def write_version_file(root, version):
|
|
version_file = os.path.join(root, "miasm", "VERSION")
|
|
os.makedirs(os.path.dirname(version_file), exist_ok=True)
|
|
with open(version_file, "w", encoding="utf-8") as fdesc:
|
|
fdesc.write(version)
|
|
|
|
|
|
class MiasmBuildPy(build_py):
|
|
def run(self):
|
|
super().run()
|
|
write_version_file(self.build_lib, self.distribution.get_version())
|
|
|
|
|
|
class MiasmSdist(sdist):
|
|
def make_release_tree(self, base_dir, files):
|
|
super().make_release_tree(base_dir, files)
|
|
write_version_file(base_dir, self.distribution.get_version())
|
|
|
|
|
|
class MiasmBuildExt(build_ext):
|
|
def build_extensions(self):
|
|
if is_mac:
|
|
linker_so = getattr(self.compiler, "linker_so", None)
|
|
if isinstance(linker_so, list):
|
|
self.compiler.linker_so = [
|
|
"-dynamiclib" if arg == "-bundle" else arg
|
|
for arg in linker_so
|
|
]
|
|
super().build_extensions()
|
|
|
|
def run(self):
|
|
super().run()
|
|
if is_win and self.extensions:
|
|
self.copy_windows_import_libs()
|
|
|
|
def copy_windows_import_libs(self):
|
|
build_roots = {
|
|
os.path.abspath("build"),
|
|
os.path.abspath(os.path.dirname(self.build_lib)),
|
|
os.path.abspath(os.path.dirname(self.build_temp)),
|
|
}
|
|
libs = []
|
|
for build_root in build_roots:
|
|
if not os.path.isdir(build_root):
|
|
continue
|
|
for root, _, files in os.walk(build_root):
|
|
for filename in files:
|
|
if filename.endswith(".lib"):
|
|
libs.append(os.path.join(root, filename))
|
|
|
|
for lib in libs:
|
|
filename = os.path.basename(lib)
|
|
dst_dir = os.path.join(self.build_lib, "miasm", "jitter")
|
|
# Windows import libraries are named after the built extension,
|
|
# e.g. VmMngr.cp313-win_amd64.lib.
|
|
if not any(
|
|
fnmatch.fnmatch(filename, pattern)
|
|
for pattern in ["VmMngr.*lib", "Jitgcc.*lib", "Jitllvm.*lib"]
|
|
):
|
|
dst_dir = os.path.join(dst_dir, "arch")
|
|
os.makedirs(dst_dir, exist_ok=True)
|
|
dst = os.path.join(dst_dir, filename)
|
|
if os.path.abspath(lib) == os.path.abspath(dst):
|
|
continue
|
|
if not os.path.isfile(dst):
|
|
print("Copying", lib, "to", dst)
|
|
copy2(lib, dst)
|
|
|
|
|
|
setup(
|
|
ext_modules=configured_ext_modules(),
|
|
cmdclass={
|
|
"build_ext": MiasmBuildExt,
|
|
"build_py": MiasmBuildPy,
|
|
"sdist": MiasmSdist,
|
|
},
|
|
)
|