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('_NSGetEnviron', 'crt_externs.h', 42 'HAVE_NSGETENVIRON') 43 configure.write_by_template( 44 os.path.join(src_dir, 'config.h.in'), 45 os.path.join(dst_dir, 'config.h')) 46 47 target = create_shared_library('ear', toolset) 48 target.add_include(dst_dir) 49 target.add_sources('ear.c') 50 target.link_against(toolset.dl_libraries()) 51 target.link_against(['pthread']) 52 target.build_release(dst_dir) 53 54 return os.path.join(dst_dir, target.name) 55 56 except Exception: 57 logging.info("Could not build interception library.", exc_info=True) 58 return None 59 60 61def execute(cmd, *args, **kwargs): 62 """ Make subprocess execution silent. """ 63 64 import subprocess 65 kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) 66 return subprocess.check_call(cmd, *args, **kwargs) 67 68 69@contextlib.contextmanager 70def TemporaryDirectory(**kwargs): 71 name = tempfile.mkdtemp(**kwargs) 72 try: 73 yield name 74 finally: 75 shutil.rmtree(name) 76 77 78class Toolset(object): 79 """ Abstract class to represent different toolset. """ 80 81 def __init__(self, src_dir): 82 self.src_dir = src_dir 83 self.compiler = None 84 self.c_flags = [] 85 86 def set_compiler(self, compiler): 87 """ part of public interface """ 88 self.compiler = compiler 89 90 def set_language_standard(self, standard): 91 """ part of public interface """ 92 self.c_flags.append('-std=' + standard) 93 94 def add_definitions(self, defines): 95 """ part of public interface """ 96 self.c_flags.extend(defines) 97 98 def dl_libraries(self): 99 raise NotImplementedError() 100 101 def shared_library_name(self, name): 102 raise NotImplementedError() 103 104 def shared_library_c_flags(self, release): 105 extra = ['-DNDEBUG', '-O3'] if release else [] 106 return extra + ['-fPIC'] + self.c_flags 107 108 def shared_library_ld_flags(self, release, name): 109 raise NotImplementedError() 110 111 112class DarwinToolset(Toolset): 113 def __init__(self, src_dir): 114 Toolset.__init__(self, src_dir) 115 116 def dl_libraries(self): 117 return [] 118 119 def shared_library_name(self, name): 120 return 'lib' + name + '.dylib' 121 122 def shared_library_ld_flags(self, release, name): 123 extra = ['-dead_strip'] if release else [] 124 return extra + ['-dynamiclib', '-install_name', '@rpath/' + name] 125 126 127class UnixToolset(Toolset): 128 def __init__(self, src_dir): 129 Toolset.__init__(self, src_dir) 130 131 def dl_libraries(self): 132 return [] 133 134 def shared_library_name(self, name): 135 return 'lib' + name + '.so' 136 137 def shared_library_ld_flags(self, release, name): 138 extra = [] if release else [] 139 return extra + ['-shared', '-Wl,-soname,' + name] 140 141 142class LinuxToolset(UnixToolset): 143 def __init__(self, src_dir): 144 UnixToolset.__init__(self, src_dir) 145 146 def dl_libraries(self): 147 return ['dl'] 148 149 150def make_toolset(src_dir): 151 platform = sys.platform 152 if platform in {'win32', 'cygwin'}: 153 raise RuntimeError('not implemented on this platform') 154 elif platform == 'darwin': 155 return DarwinToolset(src_dir) 156 elif platform in {'linux', 'linux2'}: 157 return LinuxToolset(src_dir) 158 else: 159 return UnixToolset(src_dir) 160 161 162class Configure(object): 163 def __init__(self, toolset): 164 self.ctx = toolset 165 self.results = {'APPLE': sys.platform == 'darwin'} 166 167 def _try_to_compile_and_link(self, source): 168 try: 169 with TemporaryDirectory() as work_dir: 170 src_file = 'check.c' 171 with open(os.path.join(work_dir, src_file), 'w') as handle: 172 handle.write(source) 173 174 execute([self.ctx.compiler, src_file] + self.ctx.c_flags, 175 cwd=work_dir) 176 return True 177 except Exception: 178 return False 179 180 def check_function_exists(self, function, name): 181 template = "int FUNCTION(); int main() { return FUNCTION(); }" 182 source = template.replace("FUNCTION", function) 183 184 logging.debug('Checking function %s', function) 185 found = self._try_to_compile_and_link(source) 186 logging.debug('Checking function %s -- %s', function, 187 'found' if found else 'not found') 188 self.results.update({name: found}) 189 190 def check_symbol_exists(self, symbol, include, name): 191 template = """#include <INCLUDE> 192 int main() { return ((int*)(&SYMBOL))[0]; }""" 193 source = template.replace('INCLUDE', include).replace("SYMBOL", symbol) 194 195 logging.debug('Checking symbol %s', symbol) 196 found = self._try_to_compile_and_link(source) 197 logging.debug('Checking symbol %s -- %s', symbol, 198 'found' if found else 'not found') 199 self.results.update({name: found}) 200 201 def write_by_template(self, template, output): 202 def transform(line, definitions): 203 204 pattern = re.compile(r'^#cmakedefine\s+(\S+)') 205 m = pattern.match(line) 206 if m: 207 key = m.group(1) 208 if key not in definitions or not definitions[key]: 209 return '/* #undef {0} */{1}'.format(key, os.linesep) 210 else: 211 return '#define {0}{1}'.format(key, os.linesep) 212 return line 213 214 with open(template, 'r') as src_handle: 215 logging.debug('Writing config to %s', output) 216 with open(output, 'w') as dst_handle: 217 for line in src_handle: 218 dst_handle.write(transform(line, self.results)) 219 220 221def do_configure(toolset): 222 return Configure(toolset) 223 224 225class SharedLibrary(object): 226 def __init__(self, name, toolset): 227 self.name = toolset.shared_library_name(name) 228 self.ctx = toolset 229 self.inc = [] 230 self.src = [] 231 self.lib = [] 232 233 def add_include(self, directory): 234 self.inc.extend(['-I', directory]) 235 236 def add_sources(self, source): 237 self.src.append(source) 238 239 def link_against(self, libraries): 240 self.lib.extend(['-l' + lib for lib in libraries]) 241 242 def build_release(self, directory): 243 for src in self.src: 244 logging.debug('Compiling %s', src) 245 execute( 246 [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src), 247 '-o', src + '.o'] + self.inc + 248 self.ctx.shared_library_c_flags(True), 249 cwd=directory) 250 logging.debug('Linking %s', self.name) 251 execute( 252 [self.ctx.compiler] + [src + '.o' for src in self.src] + 253 ['-o', self.name] + self.lib + 254 self.ctx.shared_library_ld_flags(True, self.name), 255 cwd=directory) 256 257 258def create_shared_library(name, toolset): 259 return SharedLibrary(name, toolset) 260