# -*- coding: utf-8 -*- # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """ This module compiles the intercept library. """ import sys import os import os.path import re import tempfile import shutil import contextlib import logging __all__ = ['build_libear'] def build_libear(compiler, dst_dir): """ Returns the full path to the 'libear' library. """ try: src_dir = os.path.dirname(os.path.realpath(__file__)) toolset = make_toolset(src_dir) toolset.set_compiler(compiler) toolset.set_language_standard('c99') toolset.add_definitions(['-D_GNU_SOURCE']) configure = do_configure(toolset) configure.check_function_exists('execve', 'HAVE_EXECVE') configure.check_function_exists('execv', 'HAVE_EXECV') configure.check_function_exists('execvpe', 'HAVE_EXECVPE') configure.check_function_exists('execvp', 'HAVE_EXECVP') configure.check_function_exists('execvP', 'HAVE_EXECVP2') configure.check_function_exists('exect', 'HAVE_EXECT') configure.check_function_exists('execl', 'HAVE_EXECL') configure.check_function_exists('execlp', 'HAVE_EXECLP') configure.check_function_exists('execle', 'HAVE_EXECLE') configure.check_function_exists('posix_spawn', 'HAVE_POSIX_SPAWN') configure.check_function_exists('posix_spawnp', 'HAVE_POSIX_SPAWNP') configure.check_symbol_exists('_NSGetEnviron', 'crt_externs.h', 'HAVE_NSGETENVIRON') configure.write_by_template( os.path.join(src_dir, 'config.h.in'), os.path.join(dst_dir, 'config.h')) target = create_shared_library('ear', toolset) target.add_include(dst_dir) target.add_sources('ear.c') target.link_against(toolset.dl_libraries()) target.link_against(['pthread']) target.build_release(dst_dir) return os.path.join(dst_dir, target.name) except Exception: logging.info("Could not build interception library.", exc_info=True) return None def execute(cmd, *args, **kwargs): """ Make subprocess execution silent. """ import subprocess kwargs.update({'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}) return subprocess.check_call(cmd, *args, **kwargs) @contextlib.contextmanager def TemporaryDirectory(**kwargs): name = tempfile.mkdtemp(**kwargs) try: yield name finally: shutil.rmtree(name) class Toolset(object): """ Abstract class to represent different toolset. """ def __init__(self, src_dir): self.src_dir = src_dir self.compiler = None self.c_flags = [] def set_compiler(self, compiler): """ part of public interface """ self.compiler = compiler def set_language_standard(self, standard): """ part of public interface """ self.c_flags.append('-std=' + standard) def add_definitions(self, defines): """ part of public interface """ self.c_flags.extend(defines) def dl_libraries(self): raise NotImplementedError() def shared_library_name(self, name): raise NotImplementedError() def shared_library_c_flags(self, release): extra = ['-DNDEBUG', '-O3'] if release else [] return extra + ['-fPIC'] + self.c_flags def shared_library_ld_flags(self, release, name): raise NotImplementedError() class DarwinToolset(Toolset): def __init__(self, src_dir): Toolset.__init__(self, src_dir) def dl_libraries(self): return [] def shared_library_name(self, name): return 'lib' + name + '.dylib' def shared_library_ld_flags(self, release, name): extra = ['-dead_strip'] if release else [] return extra + ['-dynamiclib', '-install_name', '@rpath/' + name] class UnixToolset(Toolset): def __init__(self, src_dir): Toolset.__init__(self, src_dir) def dl_libraries(self): return [] def shared_library_name(self, name): return 'lib' + name + '.so' def shared_library_ld_flags(self, release, name): extra = [] if release else [] return extra + ['-shared', '-Wl,-soname,' + name] class LinuxToolset(UnixToolset): def __init__(self, src_dir): UnixToolset.__init__(self, src_dir) def dl_libraries(self): return ['dl'] def make_toolset(src_dir): platform = sys.platform if platform in {'win32', 'cygwin'}: raise RuntimeError('not implemented on this platform') elif platform == 'darwin': return DarwinToolset(src_dir) elif platform in {'linux', 'linux2'}: return LinuxToolset(src_dir) else: return UnixToolset(src_dir) class Configure(object): def __init__(self, toolset): self.ctx = toolset self.results = {'APPLE': sys.platform == 'darwin'} def _try_to_compile_and_link(self, source): try: with TemporaryDirectory() as work_dir: src_file = 'check.c' with open(os.path.join(work_dir, src_file), 'w') as handle: handle.write(source) execute([self.ctx.compiler, src_file] + self.ctx.c_flags, cwd=work_dir) return True except Exception: return False def check_function_exists(self, function, name): template = "int FUNCTION(); int main() { return FUNCTION(); }" source = template.replace("FUNCTION", function) logging.debug('Checking function %s', function) found = self._try_to_compile_and_link(source) logging.debug('Checking function %s -- %s', function, 'found' if found else 'not found') self.results.update({name: found}) def check_symbol_exists(self, symbol, include, name): template = """#include int main() { return ((int*)(&SYMBOL))[0]; }""" source = template.replace('INCLUDE', include).replace("SYMBOL", symbol) logging.debug('Checking symbol %s', symbol) found = self._try_to_compile_and_link(source) logging.debug('Checking symbol %s -- %s', symbol, 'found' if found else 'not found') self.results.update({name: found}) def write_by_template(self, template, output): def transform(line, definitions): pattern = re.compile(r'^#cmakedefine\s+(\S+)') m = pattern.match(line) if m: key = m.group(1) if key not in definitions or not definitions[key]: return '/* #undef {0} */{1}'.format(key, os.linesep) else: return '#define {0}{1}'.format(key, os.linesep) return line with open(template, 'r') as src_handle: logging.debug('Writing config to %s', output) with open(output, 'w') as dst_handle: for line in src_handle: dst_handle.write(transform(line, self.results)) def do_configure(toolset): return Configure(toolset) class SharedLibrary(object): def __init__(self, name, toolset): self.name = toolset.shared_library_name(name) self.ctx = toolset self.inc = [] self.src = [] self.lib = [] def add_include(self, directory): self.inc.extend(['-I', directory]) def add_sources(self, source): self.src.append(source) def link_against(self, libraries): self.lib.extend(['-l' + lib for lib in libraries]) def build_release(self, directory): for src in self.src: logging.debug('Compiling %s', src) execute( [self.ctx.compiler, '-c', os.path.join(self.ctx.src_dir, src), '-o', src + '.o'] + self.inc + self.ctx.shared_library_c_flags(True), cwd=directory) logging.debug('Linking %s', self.name) execute( [self.ctx.compiler] + [src + '.o' for src in self.src] + ['-o', self.name] + self.lib + self.ctx.shared_library_ld_flags(True, self.name), cwd=directory) def create_shared_library(name, toolset): return SharedLibrary(name, toolset)