blob: d34a9a9045b7150c2415e3a99052b4ee8e8b2211 [file] [log] [blame]
Ryan Prichard41f19702019-12-23 13:21:42 -08001#!/usr/bin/env python3
2#
3# Copyright (C) 2019 The Android Open Source Project
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in
13# the documentation and/or other materials provided with the
14# distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
23# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27# SUCH DAMAGE.
28
29# Generate a benchmark using a JSON dump of ELF file symbols and relocations.
30
31import argparse
32import codecs
33import json
34import math
35import os
36import re
37import shlex
38import shutil
39import subprocess
40import sys
41import tempfile
42import textwrap
43import typing
44from enum import Enum
45from typing import Dict, List, Optional, Set
46from subprocess import PIPE, DEVNULL
47from pathlib import Path
48
49from common_types import LoadedLibrary, SymbolRef, SymKind, bfs_walk, json_to_elf_tree
50
51
52g_obfuscate = True
53g_benchmark_name = 'linker_reloc_bench'
54
55
56kBionicSonames: Set[str] = set([
57 'libc.so',
58 'libdl.so',
59 'libdl_android.so',
60 'libm.so',
61 'ld-android.so',
62])
63
64# Skip these symbols so the benchmark runs on multiple C libraries (glibc, Bionic, musl).
Elliott Hughes8c936b42020-06-15 11:18:43 -070065kBionicIgnoredSymbols: Set[str] = set([
Ryan Prichard41f19702019-12-23 13:21:42 -080066 '__FD_ISSET_chk',
67 '__FD_SET_chk',
68 '__assert',
69 '__assert2',
70 '__b64_ntop',
71 '__cmsg_nxthdr',
72 '__cxa_thread_atexit_impl',
73 '__errno',
74 '__gnu_basename',
75 '__gnu_strerror_r',
76 '__memcpy_chk',
77 '__memmove_chk',
78 '__memset_chk',
79 '__open_2',
80 '__openat_2',
81 '__pread64_chk',
82 '__pread_chk',
83 '__read_chk',
84 '__readlink_chk',
85 '__register_atfork',
86 '__sF',
87 '__strcat_chk',
88 '__strchr_chk',
89 '__strcpy_chk',
90 '__strlcat_chk',
91 '__strlcpy_chk',
92 '__strlen_chk',
93 '__strncat_chk',
94 '__strncpy_chk',
95 '__strncpy_chk2',
96 '__strrchr_chk',
97 '__system_property_area_serial',
98 '__system_property_find',
99 '__system_property_foreach',
100 '__system_property_get',
101 '__system_property_read',
102 '__system_property_serial',
103 '__system_property_set',
104 '__umask_chk',
105 '__vsnprintf_chk',
106 '__vsprintf_chk',
107 'android_dlopen_ext',
108 'android_set_abort_message',
109 'arc4random_buf',
110 'dl_unwind_find_exidx',
111 'fts_close',
112 'fts_open',
113 'fts_read',
114 'fts_set',
115 'getprogname',
116 'gettid',
117 'isnanf',
Colin Cross61c0dce2023-06-15 13:43:57 -0700118 'lseek64',
119 'lstat64',
Ryan Prichard41f19702019-12-23 13:21:42 -0800120 'mallinfo',
121 'malloc_info',
Colin Cross61c0dce2023-06-15 13:43:57 -0700122 'pread64',
Ryan Prichard41f19702019-12-23 13:21:42 -0800123 'pthread_gettid_np',
Colin Cross61c0dce2023-06-15 13:43:57 -0700124 'pwrite64',
Ryan Prichard41f19702019-12-23 13:21:42 -0800125 'res_mkquery',
126 'strlcpy',
127 'strtoll_l',
128 'strtoull_l',
129 'tgkill',
130])
131
132
133Definitions = Dict[str, LoadedLibrary]
134
135def build_symbol_index(lib: LoadedLibrary) -> Definitions:
136 defs: Dict[str, LoadedLibrary] = {}
137 for lib in bfs_walk(lib):
138 for sym in lib.syms.values():
139 if not sym.defined: continue
140 defs.setdefault(sym.name, lib)
141 return defs
142
143
Elliott Hughes68ae6ad2020-07-21 16:11:30 -0700144def check_rels(root: LoadedLibrary, defs: Definitions) -> None:
Ryan Prichard41f19702019-12-23 13:21:42 -0800145 # Find every symbol for every relocation in the load group.
146 has_missing = False
147 for lib in bfs_walk(root):
148 rels = lib.rels
149 for sym in rels.got + rels.jump_slots + [sym for off, sym in rels.symbolic]:
150 if sym.name not in defs:
151 if sym.is_weak:
152 pass # print('info: weak undefined', lib.soname, r)
153 else:
154 print(f'error: {lib.soname}: unresolved relocation to {sym.name}')
155 has_missing = True
156 if has_missing: sys.exit('error: had unresolved relocations')
157
158
159# Obscure names to avoid polluting Android code search.
160def rot13(text: str) -> str:
161 if g_obfuscate:
162 result = codecs.getencoder("rot-13")(text)[0]
163 assert isinstance(result, str)
164 return result
165 else:
166 return text
167
168
169def make_asm_file(lib: LoadedLibrary, is_main: bool, out_filename: Path, map_out_filename: Path,
170 defs: Definitions) -> bool:
171
172 def trans_sym(name: str, ver: Optional[str]) -> Optional[str]:
173 nonlocal defs
174 d = defs.get(name)
175 if d is not None and d.soname in kBionicSonames:
Elliott Hughes8c936b42020-06-15 11:18:43 -0700176 if name in kBionicIgnoredSymbols: return None
Ryan Prichard41f19702019-12-23 13:21:42 -0800177 # Discard relocations to newer Bionic symbols, because there aren't many of them, and
178 # they would limit where the benchmark can run.
179 if ver == 'LIBC': return name
180 return None
181 return 'b_' + rot13(name)
182
183 versions: Dict[Optional[str], List[str]] = {}
184
185 with open(out_filename, 'w') as out:
186 out.write(f'// AUTO-GENERATED BY {os.path.basename(__file__)} -- do not edit manually\n')
187 out.write(f'#include "{g_benchmark_name}_asm.h"\n')
188 out.write('.data\n')
189 out.write('.p2align 4\n')
190
191 if is_main:
192 out.write('.text\n' 'MAIN\n')
193
194 for d in lib.syms.values():
195 if not d.defined: continue
196 sym = trans_sym(d.name, None)
197 if sym is None: continue
198 if d.kind == SymKind.Func:
199 out.write('.text\n'
200 f'.globl {sym}\n'
201 f'.type {sym},%function\n'
202 f'{sym}:\n'
203 'nop\n')
204 else: # SymKind.Var
205 out.write('.data\n'
206 f'.globl {sym}\n'
207 f'.type {sym},%object\n'
208 f'{sym}:\n'
209 f'.space __SIZEOF_POINTER__\n')
210 versions.setdefault(d.ver_name, []).append(sym)
211
212 out.write('.text\n')
213 for r in lib.rels.jump_slots:
214 sym = trans_sym(r.name, r.ver)
215 if sym is None: continue
216 if r.is_weak: out.write(f'.weak {sym}\n')
217 out.write(f'CALL({sym})\n')
218 out.write('.text\n')
219 for r in lib.rels.got:
220 sym = trans_sym(r.name, r.ver)
221 if sym is None: continue
222 if r.is_weak: out.write(f'.weak {sym}\n')
223 out.write(f'GOT_RELOC({sym})\n')
224
225 out.write('.data\n')
226 out.write('local_label:\n')
227
228 image = []
229 for off in lib.rels.relative:
230 image.append((off, f'DATA_WORD(local_label)\n'))
231 for off, r in lib.rels.symbolic:
232 sym = trans_sym(r.name, r.ver)
233 if sym is None: continue
234 text = f'DATA_WORD({sym})\n'
235 if r.is_weak: text += f'.weak {sym}\n'
236 image.append((off, text))
237 image.sort()
238
239 cur_off = 0
240 for off, text in image:
241 if cur_off < off:
242 out.write(f'.space (__SIZEOF_POINTER__ * {off - cur_off})\n')
243 cur_off = off
244 out.write(text)
245 cur_off += 1
246
247 has_map_file = False
248 if len(versions) > 0 and list(versions.keys()) != [None]:
249 has_map_file = True
250 with open(map_out_filename, 'w') as out:
251 if None in versions:
252 print(f'error: {out_filename} has both unversioned and versioned symbols')
253 print(versions.keys())
254 sys.exit(1)
255 for ver in sorted(versions.keys()):
256 assert ver is not None
257 out.write(f'{rot13(ver)} {{\n')
258 if len(versions[ver]) > 0:
259 out.write(' global:\n')
260 out.write(''.join(f' {x};\n' for x in versions[ver]))
261 out.write(f'}};\n')
262
263 return has_map_file
264
265
266class LibNames:
267 def __init__(self, root: LoadedLibrary):
268 self._root = root
269 self._names: Dict[LoadedLibrary, str] = {}
270 all_libs = [x for x in bfs_walk(root) if x is not root and x.soname not in kBionicSonames]
271 num_digits = math.ceil(math.log10(len(all_libs) + 1))
272 if g_obfuscate:
273 self._names = {x : f'{i:0{num_digits}}' for i, x in enumerate(all_libs)}
274 else:
275 self._names = {x : re.sub(r'\.so$', '', x.soname) for x in all_libs}
276
277 def name(self, lib: LoadedLibrary) -> str:
278 if lib is self._root:
279 return f'{g_benchmark_name}_main'
280 else:
281 return f'lib{g_benchmark_name}_{self._names[lib]}'
282
283
284# Generate a ninja file directly that builds the benchmark using a C compiler driver and ninja.
285# Using a driver directly can be faster than building with Soong, and it allows testing
286# configurations that Soong can't target, like musl.
287def make_ninja_benchmark(root: LoadedLibrary, defs: Definitions, cc: str, out: Path) -> None:
288
289 lib_names = LibNames(root)
290
291 def lib_dso_name(lib: LoadedLibrary) -> str:
292 return lib_names.name(lib) + '.so'
293
294 ninja = open(out / 'build.ninja', 'w')
295 include_path = os.path.relpath(os.path.dirname(__file__) + '/../include', out)
296 common_flags = f"-Wl,-rpath-link,. -lm -I{include_path}"
297 ninja.write(textwrap.dedent(f'''\
298 rule exe
299 command = {cc} -fpie -pie $in -o $out {common_flags} $extra_args
300 rule dso
301 command = {cc} -fpic -shared $in -o $out -Wl,-soname,$out {common_flags} $extra_args
302 '''))
303
304 for lib in bfs_walk(root):
305 if lib.soname in kBionicSonames: continue
306
307 lib_base_name = lib_names.name(lib)
308 asm_name = lib_base_name + '.S'
309 map_name = lib_base_name + '.map'
310 asm_path = out / asm_name
311 map_path = out / map_name
312
313 has_map_file = make_asm_file(lib, lib is root, asm_path, map_path, defs)
314 needed = ' '.join([lib_dso_name(x) for x in lib.needed if x.soname not in kBionicSonames])
315
316 if lib is root:
317 ninja.write(f'build {lib_base_name}: exe {asm_name} {needed}\n')
318 else:
319 ninja.write(f'build {lib_dso_name(lib)}: dso {asm_name} {needed}\n')
320 if has_map_file:
321 ninja.write(f' extra_args = -Wl,--version-script={map_name}\n')
322
323 ninja.close()
324
325 subprocess.run(['ninja', '-C', str(out), lib_names.name(root)], check=True)
326
327
328def make_soong_benchmark(root: LoadedLibrary, defs: Definitions, out: Path) -> None:
329
330 lib_names = LibNames(root)
331
332 bp = open(out / 'Android.bp', 'w')
333 bp.write(f'// AUTO-GENERATED BY {os.path.basename(__file__)} -- do not edit\n')
334
Elliott Hughesf5a97dc2021-02-19 15:39:27 -0800335 bp.write(f'package {{ default_applicable_licenses: ["bionic_benchmarks_license"], }}\n')
Ryan Prichard41f19702019-12-23 13:21:42 -0800336 bp.write(f'cc_defaults {{\n')
337 bp.write(f' name: "{g_benchmark_name}_all_libs",\n')
338 bp.write(f' runtime_libs: [\n')
339 for lib in bfs_walk(root):
340 if lib.soname in kBionicSonames: continue
341 if lib is root: continue
342 bp.write(f' "{lib_names.name(lib)}",\n')
343 bp.write(f' ],\n')
344 bp.write(f'}}\n')
345
346 for lib in bfs_walk(root):
347 if lib.soname in kBionicSonames: continue
348
349 lib_base_name = lib_names.name(lib)
350 asm_name = lib_base_name + '.S'
351 map_name = lib_base_name + '.map'
352 asm_path = out / asm_name
353 map_path = out / map_name
354
355 has_map_file = make_asm_file(lib, lib is root, asm_path, map_path, defs)
356
357 if lib is root:
358 bp.write(f'cc_binary {{\n')
359 bp.write(f' defaults: ["{g_benchmark_name}_binary"],\n')
360 else:
361 bp.write(f'cc_test_library {{\n')
362 bp.write(f' defaults: ["{g_benchmark_name}_library"],\n')
363 bp.write(f' name: "{lib_base_name}",\n')
364 bp.write(f' srcs: ["{asm_name}"],\n')
365 bp.write(f' shared_libs: [\n')
366 for need in lib.needed:
367 if need.soname in kBionicSonames: continue
368 bp.write(f' "{lib_names.name(need)}",\n')
369 bp.write(f' ],\n')
370 if has_map_file:
371 bp.write(f' version_script: "{map_name}",\n')
372 bp.write('}\n')
373
374 bp.close()
375
376
377def main() -> None:
378 parser = argparse.ArgumentParser()
379 parser.add_argument('input', type=str)
380 parser.add_argument('out_dir', type=str)
381 parser.add_argument('--ninja', action='store_true',
382 help='Generate a benchmark using a compiler and ninja rather than Soong')
383 parser.add_argument('--cc',
384 help='For --ninja, a target-specific C clang driver and flags (e.g. "'
385 '$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang'
386 ' -fuse-ld=lld")')
387
388 args = parser.parse_args()
389
390 if args.ninja:
391 if args.cc is None: sys.exit('error: --cc required with --ninja')
392
393 out = Path(args.out_dir)
394 with open(Path(args.input)) as f:
395 root = json_to_elf_tree(json.load(f))
396 defs = build_symbol_index(root)
Elliott Hughes68ae6ad2020-07-21 16:11:30 -0700397 check_rels(root, defs)
Ryan Prichard41f19702019-12-23 13:21:42 -0800398
399 if out.exists(): shutil.rmtree(out)
400 os.makedirs(str(out))
401
402 if args.ninja:
403 make_ninja_benchmark(root, defs, args.cc, out)
404 else:
405 make_soong_benchmark(root, defs, out)
406
407
408if __name__ == '__main__':
409 main()