blob: fa6f1a1048353344e6c409a971f79076dc9c36dc [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
Elliott Hughes4c41cab2024-01-20 00:30:51 +000049from common_types import LoadedLibrary, SymbolRef, SymBind, SymKind, bfs_walk, json_to_elf_tree
Ryan Prichard41f19702019-12-23 13:21:42 -080050
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
Elliott Hughes4c41cab2024-01-20 00:30:51 +0000198 binding = 'weak' if d.bind == SymBind.Weak else 'globl'
Ryan Prichard41f19702019-12-23 13:21:42 -0800199 if d.kind == SymKind.Func:
200 out.write('.text\n'
Elliott Hughes4c41cab2024-01-20 00:30:51 +0000201 f'.{binding} {sym}\n'
Ryan Prichard41f19702019-12-23 13:21:42 -0800202 f'.type {sym},%function\n'
203 f'{sym}:\n'
204 'nop\n')
205 else: # SymKind.Var
206 out.write('.data\n'
Elliott Hughes4c41cab2024-01-20 00:30:51 +0000207 f'.{binding} {sym}\n'
Ryan Prichard41f19702019-12-23 13:21:42 -0800208 f'.type {sym},%object\n'
209 f'{sym}:\n'
210 f'.space __SIZEOF_POINTER__\n')
211 versions.setdefault(d.ver_name, []).append(sym)
212
213 out.write('.text\n')
214 for r in lib.rels.jump_slots:
215 sym = trans_sym(r.name, r.ver)
216 if sym is None: continue
217 if r.is_weak: out.write(f'.weak {sym}\n')
218 out.write(f'CALL({sym})\n')
219 out.write('.text\n')
220 for r in lib.rels.got:
221 sym = trans_sym(r.name, r.ver)
222 if sym is None: continue
223 if r.is_weak: out.write(f'.weak {sym}\n')
224 out.write(f'GOT_RELOC({sym})\n')
225
226 out.write('.data\n')
227 out.write('local_label:\n')
228
229 image = []
230 for off in lib.rels.relative:
231 image.append((off, f'DATA_WORD(local_label)\n'))
232 for off, r in lib.rels.symbolic:
233 sym = trans_sym(r.name, r.ver)
234 if sym is None: continue
235 text = f'DATA_WORD({sym})\n'
236 if r.is_weak: text += f'.weak {sym}\n'
237 image.append((off, text))
238 image.sort()
239
240 cur_off = 0
241 for off, text in image:
242 if cur_off < off:
243 out.write(f'.space (__SIZEOF_POINTER__ * {off - cur_off})\n')
244 cur_off = off
245 out.write(text)
246 cur_off += 1
247
248 has_map_file = False
249 if len(versions) > 0 and list(versions.keys()) != [None]:
250 has_map_file = True
251 with open(map_out_filename, 'w') as out:
252 if None in versions:
253 print(f'error: {out_filename} has both unversioned and versioned symbols')
254 print(versions.keys())
255 sys.exit(1)
256 for ver in sorted(versions.keys()):
257 assert ver is not None
258 out.write(f'{rot13(ver)} {{\n')
259 if len(versions[ver]) > 0:
260 out.write(' global:\n')
261 out.write(''.join(f' {x};\n' for x in versions[ver]))
262 out.write(f'}};\n')
263
264 return has_map_file
265
266
267class LibNames:
268 def __init__(self, root: LoadedLibrary):
269 self._root = root
270 self._names: Dict[LoadedLibrary, str] = {}
271 all_libs = [x for x in bfs_walk(root) if x is not root and x.soname not in kBionicSonames]
272 num_digits = math.ceil(math.log10(len(all_libs) + 1))
273 if g_obfuscate:
274 self._names = {x : f'{i:0{num_digits}}' for i, x in enumerate(all_libs)}
275 else:
276 self._names = {x : re.sub(r'\.so$', '', x.soname) for x in all_libs}
277
278 def name(self, lib: LoadedLibrary) -> str:
279 if lib is self._root:
280 return f'{g_benchmark_name}_main'
281 else:
282 return f'lib{g_benchmark_name}_{self._names[lib]}'
283
284
285# Generate a ninja file directly that builds the benchmark using a C compiler driver and ninja.
286# Using a driver directly can be faster than building with Soong, and it allows testing
287# configurations that Soong can't target, like musl.
288def make_ninja_benchmark(root: LoadedLibrary, defs: Definitions, cc: str, out: Path) -> None:
289
290 lib_names = LibNames(root)
291
292 def lib_dso_name(lib: LoadedLibrary) -> str:
293 return lib_names.name(lib) + '.so'
294
295 ninja = open(out / 'build.ninja', 'w')
296 include_path = os.path.relpath(os.path.dirname(__file__) + '/../include', out)
297 common_flags = f"-Wl,-rpath-link,. -lm -I{include_path}"
298 ninja.write(textwrap.dedent(f'''\
299 rule exe
300 command = {cc} -fpie -pie $in -o $out {common_flags} $extra_args
301 rule dso
302 command = {cc} -fpic -shared $in -o $out -Wl,-soname,$out {common_flags} $extra_args
303 '''))
304
305 for lib in bfs_walk(root):
306 if lib.soname in kBionicSonames: continue
307
308 lib_base_name = lib_names.name(lib)
309 asm_name = lib_base_name + '.S'
310 map_name = lib_base_name + '.map'
311 asm_path = out / asm_name
312 map_path = out / map_name
313
314 has_map_file = make_asm_file(lib, lib is root, asm_path, map_path, defs)
315 needed = ' '.join([lib_dso_name(x) for x in lib.needed if x.soname not in kBionicSonames])
316
317 if lib is root:
318 ninja.write(f'build {lib_base_name}: exe {asm_name} {needed}\n')
319 else:
320 ninja.write(f'build {lib_dso_name(lib)}: dso {asm_name} {needed}\n')
321 if has_map_file:
322 ninja.write(f' extra_args = -Wl,--version-script={map_name}\n')
323
324 ninja.close()
325
326 subprocess.run(['ninja', '-C', str(out), lib_names.name(root)], check=True)
327
328
329def make_soong_benchmark(root: LoadedLibrary, defs: Definitions, out: Path) -> None:
330
331 lib_names = LibNames(root)
332
333 bp = open(out / 'Android.bp', 'w')
334 bp.write(f'// AUTO-GENERATED BY {os.path.basename(__file__)} -- do not edit\n')
335
Elliott Hughesf5a97dc2021-02-19 15:39:27 -0800336 bp.write(f'package {{ default_applicable_licenses: ["bionic_benchmarks_license"], }}\n')
Ryan Prichard41f19702019-12-23 13:21:42 -0800337 bp.write(f'cc_defaults {{\n')
338 bp.write(f' name: "{g_benchmark_name}_all_libs",\n')
339 bp.write(f' runtime_libs: [\n')
340 for lib in bfs_walk(root):
341 if lib.soname in kBionicSonames: continue
342 if lib is root: continue
343 bp.write(f' "{lib_names.name(lib)}",\n')
344 bp.write(f' ],\n')
345 bp.write(f'}}\n')
346
347 for lib in bfs_walk(root):
348 if lib.soname in kBionicSonames: continue
349
350 lib_base_name = lib_names.name(lib)
351 asm_name = lib_base_name + '.S'
352 map_name = lib_base_name + '.map'
353 asm_path = out / asm_name
354 map_path = out / map_name
355
356 has_map_file = make_asm_file(lib, lib is root, asm_path, map_path, defs)
357
358 if lib is root:
359 bp.write(f'cc_binary {{\n')
360 bp.write(f' defaults: ["{g_benchmark_name}_binary"],\n')
361 else:
362 bp.write(f'cc_test_library {{\n')
363 bp.write(f' defaults: ["{g_benchmark_name}_library"],\n')
364 bp.write(f' name: "{lib_base_name}",\n')
365 bp.write(f' srcs: ["{asm_name}"],\n')
366 bp.write(f' shared_libs: [\n')
367 for need in lib.needed:
368 if need.soname in kBionicSonames: continue
369 bp.write(f' "{lib_names.name(need)}",\n')
370 bp.write(f' ],\n')
371 if has_map_file:
372 bp.write(f' version_script: "{map_name}",\n')
373 bp.write('}\n')
374
375 bp.close()
376
377
378def main() -> None:
379 parser = argparse.ArgumentParser()
380 parser.add_argument('input', type=str)
381 parser.add_argument('out_dir', type=str)
382 parser.add_argument('--ninja', action='store_true',
383 help='Generate a benchmark using a compiler and ninja rather than Soong')
384 parser.add_argument('--cc',
385 help='For --ninja, a target-specific C clang driver and flags (e.g. "'
386 '$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang'
387 ' -fuse-ld=lld")')
388
389 args = parser.parse_args()
390
391 if args.ninja:
392 if args.cc is None: sys.exit('error: --cc required with --ninja')
393
394 out = Path(args.out_dir)
395 with open(Path(args.input)) as f:
396 root = json_to_elf_tree(json.load(f))
397 defs = build_symbol_index(root)
Elliott Hughes68ae6ad2020-07-21 16:11:30 -0700398 check_rels(root, defs)
Ryan Prichard41f19702019-12-23 13:21:42 -0800399
400 if out.exists(): shutil.rmtree(out)
401 os.makedirs(str(out))
402
403 if args.ninja:
404 make_ninja_benchmark(root, defs, args.cc, out)
405 else:
406 make_soong_benchmark(root, defs, out)
407
408
409if __name__ == '__main__':
410 main()