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-exception 5""" This module compiles the intercept library. """ 6 7import sys 8import os 9import os.path 10import re 11import tempfile 12import shutil 13import contextlib 14import logging 15 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 None 60 61 62def execute(cmd, *args, **kwargs): 63 """Make subprocess execution silent.""" 64 65 import subprocess 66 67 kwargs.update({"stdout": subprocess.PIPE, "stderr": subprocess.STDOUT}) 68 return subprocess.check_call(cmd, *args, **kwargs) 69 70 71@contextlib.contextmanager 72def TemporaryDirectory(**kwargs): 73 name = tempfile.mkdtemp(**kwargs) 74 try: 75 yield name 76 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_dir 85 self.compiler = None 86 self.c_flags = [] 87 88 def set_compiler(self, compiler): 89 """part of public interface""" 90 self.compiler = compiler 91 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_flags 109 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.platform 154 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 = toolset 167 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 True 178 except Exception: 179 return False 180 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 line 216 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 = toolset 232 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.inc 257 + 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.lib 266 + 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