1# -*- coding: utf-8 -*- 2# The LLVM Compiler Infrastructure 3# 4# This file is distributed under the University of Illinois Open Source 5# License. See LICENSE.TXT for details. 6""" This module compiles the intercept library. """ 7 8import sys 9import os 10import os.path 11import re 12import tempfile 13import shutil 14import contextlib 15import logging 16 17__all__ = ['build_libear'] 18 19 20def build_libear(compiler, dst_dir): 21 """ Returns the full path to the 'libear' library. """ 22 23 try: 24 src_dir = os.path.dirname(os.path.realpath(__file__)) 25 toolset = make_toolset(src_dir) 26 toolset.set_compiler(compiler) 27 toolset.set_language_standard('c99') 28 toolset.add_definitions(['-D_GNU_SOURCE']) 29 30 configure = do_configure(toolset) 31 configure.check_function_exists('execve', 'HAVE_EXECVE') 32 configure.check_function_exists('execv', 'HAVE_EXECV') 33 configure.check_function_exists('execvpe', 'HAVE_EXECVPE') 34 configure.check_function_exists('execvp', 'HAVE_EXECVP') 35 configure.check_function_exists('execvP', 'HAVE_EXECVP2') 36 configure.check_function_exists('exect', 'HAVE_EXECT') 37 configure.check_function_exists('execl', 'HAVE_EXECL') 38 configure.check_function_exists('execlp', 'HAVE_EXECLP') 39 configure.check_function_exists('execle', 'HAVE_EXECLE') 40 configure.check_function_exists('posix_spawn', 'HAVE_POSIX_SPAWN') 41 configure.check_function_exists('posix_spawnp', 'HAVE_POSIX_SPAWNP') 42 configure.check_symbol_exists('_NSGetEnviron', 'crt_externs.h', 43 'HAVE_NSGETENVIRON') 44 configure.write_by_template( 45 os.path.join(src_dir, 'config.h.in'), 46 os.path.join(dst_dir, 'config.h')) 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 kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) 67 return subprocess.check_call(cmd, *args, **kwargs) 68 69 70@contextlib.contextmanager 71def TemporaryDirectory(**kwargs): 72 name = tempfile.mkdtemp(**kwargs) 73 try: 74 yield name 75 finally: 76 shutil.rmtree(name) 77 78 79class Toolset(object): 80 """ Abstract class to represent different toolset. """ 81 82 def __init__(self, src_dir): 83 self.src_dir = src_dir 84 self.compiler = None 85 self.c_flags = [] 86 87 def set_compiler(self, compiler): 88 """ part of public interface """ 89 self.compiler = compiler 90 91 def set_language_standard(self, standard): 92 """ part of public interface """ 93 self.c_flags.append('-std=' + standard) 94 95 def add_definitions(self, defines): 96 """ part of public interface """ 97 self.c_flags.extend(defines) 98 99 def dl_libraries(self): 100 raise NotImplementedError() 101 102 def shared_library_name(self, name): 103 raise NotImplementedError() 104 105 def shared_library_c_flags(self, release): 106 extra = ['-DNDEBUG', '-O3'] if release else [] 107 return extra + ['-fPIC'] + self.c_flags 108 109 def shared_library_ld_flags(self, release, name): 110 raise NotImplementedError() 111 112 113class DarwinToolset(Toolset): 114 def __init__(self, src_dir): 115 Toolset.__init__(self, src_dir) 116 117 def dl_libraries(self): 118 return [] 119 120 def shared_library_name(self, name): 121 return 'lib' + name + '.dylib' 122 123 def shared_library_ld_flags(self, release, name): 124 extra = ['-dead_strip'] if release else [] 125 return extra + ['-dynamiclib', '-install_name', '@rpath/' + name] 126 127 128class UnixToolset(Toolset): 129 def __init__(self, src_dir): 130 Toolset.__init__(self, src_dir) 131 132 def dl_libraries(self): 133 return [] 134 135 def shared_library_name(self, name): 136 return 'lib' + name + '.so' 137 138 def shared_library_ld_flags(self, release, name): 139 extra = [] if release else [] 140 return extra + ['-shared', '-Wl,-soname,' + name] 141 142 143class LinuxToolset(UnixToolset): 144 def __init__(self, src_dir): 145 UnixToolset.__init__(self, src_dir) 146 147 def dl_libraries(self): 148 return ['dl'] 149 150 151def make_toolset(src_dir): 152 platform = sys.platform 153 if platform in {'win32', 'cygwin'}: 154 raise RuntimeError('not implemented on this platform') 155 elif platform == 'darwin': 156 return DarwinToolset(src_dir) 157 elif platform in {'linux', 'linux2'}: 158 return LinuxToolset(src_dir) 159 else: 160 return UnixToolset(src_dir) 161 162 163class Configure(object): 164 def __init__(self, toolset): 165 self.ctx = toolset 166 self.results = {'APPLE': sys.platform == 'darwin'} 167 168 def _try_to_compile_and_link(self, source): 169 try: 170 with TemporaryDirectory() as work_dir: 171 src_file = 'check.c' 172 with open(os.path.join(work_dir, src_file), 'w') as handle: 173 handle.write(source) 174 175 execute([self.ctx.compiler, src_file] + self.ctx.c_flags, 176 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('Checking function %s -- %s', function, 188 'found' if found else 'not found') 189 self.results.update({name: found}) 190 191 def check_symbol_exists(self, symbol, include, name): 192 template = """#include <INCLUDE> 193 int main() { return ((int*)(&SYMBOL))[0]; }""" 194 source = template.replace('INCLUDE', include).replace("SYMBOL", symbol) 195 196 logging.debug('Checking symbol %s', symbol) 197 found = self._try_to_compile_and_link(source) 198 logging.debug('Checking symbol %s -- %s', symbol, 199 'found' if found else 'not found') 200 self.results.update({name: found}) 201 202 def write_by_template(self, template, output): 203 def transform(line, definitions): 204 205 pattern = re.compile(r'^#cmakedefine\s+(\S+)') 206 m = pattern.match(line) 207 if m: 208 key = m.group(1) 209 if key not in definitions or not definitions[key]: 210 return '/* #undef {} */\n'.format(key) 211 else: 212 return '#define {}\n'.format(key) 213 return line 214 215 with open(template, 'r') as src_handle: 216 logging.debug('Writing config to %s', output) 217 with open(output, 'w') as dst_handle: 218 for line in src_handle: 219 dst_handle.write(transform(line, self.results)) 220 221 222def do_configure(toolset): 223 return Configure(toolset) 224 225 226class SharedLibrary(object): 227 def __init__(self, name, toolset): 228 self.name = toolset.shared_library_name(name) 229 self.ctx = toolset 230 self.inc = [] 231 self.src = [] 232 self.lib = [] 233 234 def add_include(self, directory): 235 self.inc.extend(['-I', directory]) 236 237 def add_sources(self, source): 238 self.src.append(source) 239 240 def link_against(self, libraries): 241 self.lib.extend(['-l' + lib for lib in libraries]) 242 243 def build_release(self, directory): 244 for src in self.src: 245 logging.debug('Compiling %s', src) 246 execute( 247 [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src), 248 '-o', src + '.o'] + self.inc + 249 self.ctx.shared_library_c_flags(True), 250 cwd=directory) 251 logging.debug('Linking %s', self.name) 252 execute( 253 [self.ctx.compiler] + [src + '.o' for src in self.src] + 254 ['-o', self.name] + self.lib + 255 self.ctx.shared_library_ld_flags(True, self.name), 256 cwd=directory) 257 258 259def create_shared_library(name, toolset): 260 return SharedLibrary(name, toolset) 261