blob: 0dfe8c11eba0291d2b80cd225dce9c60d00147c6 [file] [log] [blame]
Laszlo Nagybc687582016-01-12 22:38:41 +00001# -*- coding: utf-8 -*-
Chandler Carruth2946cd72019-01-19 08:50:56 +00002# 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
Laszlo Nagybc687582016-01-12 22:38:41 +00005""" 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]:
Laszlo Nagyed739d92017-03-08 09:27:53 +0000209 return '/* #undef {0} */{1}'.format(key, os.linesep)
Laszlo Nagybc687582016-01-12 22:38:41 +0000210 else:
Laszlo Nagyed739d92017-03-08 09:27:53 +0000211 return '#define {0}{1}'.format(key, os.linesep)
Laszlo Nagybc687582016-01-12 22:38:41 +0000212 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)