273 lines · python
1# -*- coding: utf-8 -*-2# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3# See https://llvm.org/LICENSE.txt for license information.4# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5""" This module compiles the intercept library. """6 7import sys8import os9import os.path10import re11import tempfile12import shutil13import contextlib14import logging15 16__all__ = ["build_libear"]17 18 19def build_libear(compiler, dst_dir):20 """Returns the full path to the 'libear' library."""21 22 try:23 src_dir = os.path.dirname(os.path.realpath(__file__))24 toolset = make_toolset(src_dir)25 toolset.set_compiler(compiler)26 toolset.set_language_standard("c99")27 toolset.add_definitions(["-D_GNU_SOURCE"])28 29 configure = do_configure(toolset)30 configure.check_function_exists("execve", "HAVE_EXECVE")31 configure.check_function_exists("execv", "HAVE_EXECV")32 configure.check_function_exists("execvpe", "HAVE_EXECVPE")33 configure.check_function_exists("execvp", "HAVE_EXECVP")34 configure.check_function_exists("execvP", "HAVE_EXECVP2")35 configure.check_function_exists("exect", "HAVE_EXECT")36 configure.check_function_exists("execl", "HAVE_EXECL")37 configure.check_function_exists("execlp", "HAVE_EXECLP")38 configure.check_function_exists("execle", "HAVE_EXECLE")39 configure.check_function_exists("posix_spawn", "HAVE_POSIX_SPAWN")40 configure.check_function_exists("posix_spawnp", "HAVE_POSIX_SPAWNP")41 configure.check_symbol_exists(42 "_NSGetEnviron", "crt_externs.h", "HAVE_NSGETENVIRON"43 )44 configure.write_by_template(45 os.path.join(src_dir, "config.h.in"), os.path.join(dst_dir, "config.h")46 )47 48 target = create_shared_library("ear", toolset)49 target.add_include(dst_dir)50 target.add_sources("ear.c")51 target.link_against(toolset.dl_libraries())52 target.link_against(["pthread"])53 target.build_release(dst_dir)54 55 return os.path.join(dst_dir, target.name)56 57 except Exception:58 logging.info("Could not build interception library.", exc_info=True)59 return None60 61 62def execute(cmd, *args, **kwargs):63 """Make subprocess execution silent."""64 65 import subprocess66 67 kwargs.update({"stdout": subprocess.PIPE, "stderr": subprocess.STDOUT})68 return subprocess.check_call(cmd, *args, **kwargs)69 70 71@contextlib.contextmanager72def TemporaryDirectory(**kwargs):73 name = tempfile.mkdtemp(**kwargs)74 try:75 yield name76 finally:77 shutil.rmtree(name)78 79 80class Toolset(object):81 """Abstract class to represent different toolset."""82 83 def __init__(self, src_dir):84 self.src_dir = src_dir85 self.compiler = None86 self.c_flags = []87 88 def set_compiler(self, compiler):89 """part of public interface"""90 self.compiler = compiler91 92 def set_language_standard(self, standard):93 """part of public interface"""94 self.c_flags.append("-std=" + standard)95 96 def add_definitions(self, defines):97 """part of public interface"""98 self.c_flags.extend(defines)99 100 def dl_libraries(self):101 raise NotImplementedError()102 103 def shared_library_name(self, name):104 raise NotImplementedError()105 106 def shared_library_c_flags(self, release):107 extra = ["-DNDEBUG", "-O3"] if release else []108 return extra + ["-fPIC"] + self.c_flags109 110 def shared_library_ld_flags(self, release, name):111 raise NotImplementedError()112 113 114class DarwinToolset(Toolset):115 def __init__(self, src_dir):116 Toolset.__init__(self, src_dir)117 118 def dl_libraries(self):119 return []120 121 def shared_library_name(self, name):122 return "lib" + name + ".dylib"123 124 def shared_library_ld_flags(self, release, name):125 extra = ["-dead_strip"] if release else []126 return extra + ["-dynamiclib", "-install_name", "@rpath/" + name]127 128 129class UnixToolset(Toolset):130 def __init__(self, src_dir):131 Toolset.__init__(self, src_dir)132 133 def dl_libraries(self):134 return []135 136 def shared_library_name(self, name):137 return "lib" + name + ".so"138 139 def shared_library_ld_flags(self, release, name):140 extra = [] if release else []141 return extra + ["-shared", "-Wl,-soname," + name]142 143 144class LinuxToolset(UnixToolset):145 def __init__(self, src_dir):146 UnixToolset.__init__(self, src_dir)147 148 def dl_libraries(self):149 return ["dl"]150 151 152def make_toolset(src_dir):153 platform = sys.platform154 if platform in {"win32", "cygwin"}:155 raise RuntimeError("not implemented on this platform")156 elif platform == "darwin":157 return DarwinToolset(src_dir)158 elif platform in {"linux", "linux2"}:159 return LinuxToolset(src_dir)160 else:161 return UnixToolset(src_dir)162 163 164class Configure(object):165 def __init__(self, toolset):166 self.ctx = toolset167 self.results = {"APPLE": sys.platform == "darwin"}168 169 def _try_to_compile_and_link(self, source):170 try:171 with TemporaryDirectory() as work_dir:172 src_file = "check.c"173 with open(os.path.join(work_dir, src_file), "w") as handle:174 handle.write(source)175 176 execute([self.ctx.compiler, src_file] + self.ctx.c_flags, cwd=work_dir)177 return True178 except Exception:179 return False180 181 def check_function_exists(self, function, name):182 template = "int FUNCTION(); int main() { return FUNCTION(); }"183 source = template.replace("FUNCTION", function)184 185 logging.debug("Checking function %s", function)186 found = self._try_to_compile_and_link(source)187 logging.debug(188 "Checking function %s -- %s", function, "found" if found else "not found"189 )190 self.results.update({name: found})191 192 def check_symbol_exists(self, symbol, include, name):193 template = """#include <INCLUDE>194 int main() { return ((int*)(&SYMBOL))[0]; }"""195 source = template.replace("INCLUDE", include).replace("SYMBOL", symbol)196 197 logging.debug("Checking symbol %s", symbol)198 found = self._try_to_compile_and_link(source)199 logging.debug(200 "Checking symbol %s -- %s", symbol, "found" if found else "not found"201 )202 self.results.update({name: found})203 204 def write_by_template(self, template, output):205 def transform(line, definitions):206 207 pattern = re.compile(r"^#cmakedefine\s+(\S+)")208 m = pattern.match(line)209 if m:210 key = m.group(1)211 if key not in definitions or not definitions[key]:212 return "/* #undef {0} */{1}".format(key, os.linesep)213 else:214 return "#define {0}{1}".format(key, os.linesep)215 return line216 217 with open(template, "r") as src_handle:218 logging.debug("Writing config to %s", output)219 with open(output, "w") as dst_handle:220 for line in src_handle:221 dst_handle.write(transform(line, self.results))222 223 224def do_configure(toolset):225 return Configure(toolset)226 227 228class SharedLibrary(object):229 def __init__(self, name, toolset):230 self.name = toolset.shared_library_name(name)231 self.ctx = toolset232 self.inc = []233 self.src = []234 self.lib = []235 236 def add_include(self, directory):237 self.inc.extend(["-I", directory])238 239 def add_sources(self, source):240 self.src.append(source)241 242 def link_against(self, libraries):243 self.lib.extend(["-l" + lib for lib in libraries])244 245 def build_release(self, directory):246 for src in self.src:247 logging.debug("Compiling %s", src)248 execute(249 [250 self.ctx.compiler,251 "-c",252 os.path.join(self.ctx.src_dir, src),253 "-o",254 src + ".o",255 ]256 + self.inc257 + self.ctx.shared_library_c_flags(True),258 cwd=directory,259 )260 logging.debug("Linking %s", self.name)261 execute(262 [self.ctx.compiler]263 + [src + ".o" for src in self.src]264 + ["-o", self.name]265 + self.lib266 + self.ctx.shared_library_ld_flags(True, self.name),267 cwd=directory,268 )269 270 271def create_shared_library(name, toolset):272 return SharedLibrary(name, toolset)273