setup.py
#! /usr/bin/env python2
from __future__ import print_function
# Reference: https://stackoverflow.com/a/13468644/1806760
from setuptools import setup, Extension
from distutils.util import get_platform
from distutils.sysconfig import get_python_lib, get_config_vars
from distutils.dist import DistributionMetadata
from distutils.command.install_data import install_data
from distutils.spawn import find_executable
import subprocess
from tempfile import TemporaryFile
import fnmatch
import io
import os
import platform
from shutil import copy2, copyfile, rmtree
import sys
import tempfile
import atexit
import re
is_win = platform.system() == "Windows"
is_mac = platform.system() == "Darwin"
is_64bit = platform.architecture()[0] == "64bit"
if is_win:
import winreg
def set_extension_compile_args(extension):
rel_lib_path = extension.name.replace(".", "/")
abs_lib_path = os.path.join(get_python_lib(), rel_lib_path)
lib_name = abs_lib_path + ".so"
extension.extra_link_args = [ "-Wl,-install_name," + lib_name]
class smart_install_data(install_data):
"""Replacement for distutils.command.install_data to handle
configuration files location.
"""
def run(self):
# install files to /etc when target was /usr(/local)/etc
self.data_files = [
(path, files) for path, files in self.data_files
if path # skip README.md or any file with an empty path
]
return install_data.run(self)
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, try to detect it
clang_cl = find_executable("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_cl = os.path.join(clang_path, "bin", "clang.exe")
stdout = subprocess.check_output("\"{}\" --version".format(clang_cl))
version = stdout.splitlines(False)[0].decode()
match = re.search(r"version (\d+\.\d+\.\d+)", version)
if match is None:
return None
version = list(map(lambda s: int(s), match.group(1).split(".")))
return version
except FileNotFoundError:
return None
def win_use_clang():
# To force python to use clang we copy the binaries in a temporary directory that's added to the PATH.
# We could use the build directory created by distutils for this, but it seems non-trivial to gather
# (https://stackoverflow.com/questions/12896367/reliable-way-to-get-the-build-directory-from-within-setup-py).
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")
copyfile(os.path.join(clang_path, "bin", "clang-cl.exe"), os.path.join(tmpdir, "cl.exe"))
# If you run the installation from a Visual Studio command prompt link.exe will already exist
# Fall back to LLVM's lld-link.exe which is compatible with link's command line
if find_executable("link") is None:
# LLVM >= 14.0.0 started supporting the /LTCG flag
# Earlier versions will error during the linking phase so bail out now
if clang_version[0] < 14:
return False
copyfile(os.path.join(clang_path, "bin", "lld-link.exe"), os.path.join(tmpdir, "link.exe"))
# Add the temporary directory at the front of the PATH and clean up on exit
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
build_extensions = True
build_warnings = []
win_force_clang = False
if is_win:
if is_64bit or find_executable("cl") is None:
# We do not change to clang if under 32 bits, because even with Clang we
# do not use uint128_t with the 32 bits ABI. Regardless we can try to
# find it when building in 32-bit mode if cl.exe was not found in the 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 = find_executable("cl")
link = find_executable("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))
def build_all():
packages=[
"miasm",
"miasm/arch",
"miasm/arch/x86",
"miasm/arch/arm",
"miasm/arch/aarch64",
"miasm/arch/msp430",
"miasm/arch/mep",
"miasm/arch/sh4",
"miasm/arch/mips32",
"miasm/arch/ppc",
"miasm/core",
"miasm/expression",
"miasm/ir",
"miasm/ir/translators",
"miasm/analysis",
"miasm/os_dep",
"miasm/os_dep/linux",
"miasm/loader",
"miasm/jitter",
"miasm/jitter/arch",
"miasm/jitter/loader",
]
ext_modules_all = [
Extension(
"miasm.jitter.VmMngr",
[
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/bn.c",
]
),
Extension(
"miasm.jitter.arch.JitCore_x86",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_x86.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_arm",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_arm.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_aarch64",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_aarch64.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_msp430",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_msp430.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_mep",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_mep.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_mips32",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_mips32.c"
]
),
Extension(
"miasm.jitter.arch.JitCore_ppc32",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_ppc32.c"
],
depends=[
"miasm/jitter/arch/JitCore_ppc32.h",
"miasm/jitter/arch/JitCore_ppc32_regs.h",
"miasm/jitter/bn.h",
]
),
Extension(
"miasm.jitter.arch.JitCore_m68k",
[
"miasm/jitter/JitCore.c",
"miasm/jitter/vm_mngr.c",
"miasm/jitter/vm_mngr_py.c",
"miasm/jitter/op_semantics.c",
"miasm/jitter/bn.c",
"miasm/jitter/arch/JitCore_m68k.c"
]
),
Extension(
"miasm.jitter.Jitllvm",
[
"miasm/jitter/Jitllvm.c",
"miasm/jitter/bn.c",
"miasm/runtime/udivmodti4.c",
"miasm/runtime/divti3.c",
"miasm/runtime/udivti3.c"
],
depends=[
"miasm/runtime/export.h",
"miasm/runtime/int_endianness.h",
"miasm/runtime/int_lib.h",
"miasm/runtime/int_types.h",
"miasm/runtime/int_util.h",
]
),
Extension("miasm.jitter.Jitgcc",
["miasm/jitter/Jitgcc.c",
"miasm/jitter/bn.c",
]),
]
if is_win:
# Force setuptools to use whatever msvc version installed
# 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_all:
extension.extra_compile_args = extra_compile_args
elif is_mac:
for extension in ext_modules_all:
set_extension_compile_args(extension)
cfg_vars = get_config_vars()
cfg_vars["LDSHARED"] = cfg_vars["LDSHARED"].replace("-bundle", "-dynamiclib")
# Do not attempt to build the extensions when disabled
if not build_extensions:
ext_modules_all = []
print("building")
if not os.path.exists("build"):
os.mkdir("build")
build_ok = False
for name, ext_modules in [("all", ext_modules_all)]:
print("build with", repr(name))
try:
s = setup(
name = "miasm",
version = __import__("miasm").VERSION,
packages = packages,
data_files=[("", ["README.md"])],
package_data = {
"miasm": [
"jitter/*.h",
"jitter/arch/*.h",
"runtime/*.h",
"VERSION"
]
},
install_requires=["future", "pyparsing>=2.4.1"],
cmdclass={"install_data": smart_install_data},
ext_modules = ext_modules,
# Metadata
author = "Fabrice Desclaux",
author_email = "serpilliere@droid-corp.org",
description = "Machine code manipulation library",
license = "GPLv2",
long_description=long_description,
long_description_content_type=long_description_content_type,
keywords = [
"reverse engineering",
"disassembler",
"emulator",
"symbolic execution",
"intermediate representation",
"assembler",
],
classifiers=[
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.6",
],
url = "http://miasm.re",
)
except SystemExit as e:
print(repr(e))
continue
build_ok = True
break
if not build_ok:
if len(build_warnings) > 0:
print("ERROR: There was an issue setting up the build environment:")
for warning in build_warnings:
print(" " + warning)
raise ValueError("Unable to build Miasm!")
print("build", name)
# we copy libraries from build dir to current miasm directory
build_base = "build"
if "build" in s.command_options:
if "build_base" in s.command_options["build"]:
build_base = s.command_options["build"]["build_base"]
print(build_base)
if is_win and build_extensions:
libs = []
for root, _, files in os.walk(build_base):
for filename in files:
if not filename.endswith(".lib"):
continue
f_path = os.path.join(root, filename)
libs.append(f_path)
lib_dirname = None
for dirname in os.listdir(build_base):
if not dirname.startswith("lib"):
continue
lib_dirname = dirname
break
jitters = []
for lib in libs:
filename = os.path.basename(lib)
dst = os.path.join(build_base, lib_dirname, "miasm", "jitter")
# Windows built libraries may have a name like VmMngr.cp38-win_amd64.lib
if not any([fnmatch.fnmatch(filename, pattern) for pattern in ["VmMngr.*lib", "Jitgcc.*lib", "Jitllvm.*lib"]]):
dst = os.path.join(dst, "arch")
dst = os.path.join(dst, filename)
if not os.path.isfile(dst):
print("Copying", lib, "to", dst)
copy2(lib, dst)
# Inform the user about the skipped build
if not build_extensions:
print("WARNING: miasm jit extensions were not compiled, details:")
for warning in build_warnings:
print(" " + warning)
with io.open(os.path.join(os.path.abspath(os.path.dirname("__file__")),
"README.md"), encoding="utf-8") as fdesc:
long_description = fdesc.read()
long_description_content_type = "text/markdown"
# Monkey patching (distutils does not handle Description-Content-Type
# from long_description_content_type parameter in setup()).
_write_pkg_file_orig = DistributionMetadata.write_pkg_file
def _write_pkg_file(self, file):
with TemporaryFile(mode="w+", encoding="utf-8") as tmpfd:
_write_pkg_file_orig(self, tmpfd)
tmpfd.seek(0)
for line in tmpfd:
if line.startswith("Metadata-Version: "):
file.write("Metadata-Version: 2.1\n")
elif line.startswith("Description: "):
file.write("Description-Content-Type: %s; charset=UTF-8\n" %
long_description_content_type)
file.write(line)
else:
file.write(line)
DistributionMetadata.write_pkg_file = _write_pkg_file
build_all()