xref: /aosp_15_r20/external/cronet/build/android/gyp/assert_static_initializers.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env python3
2# Copyright 2017 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Checks the number of static initializers in an APK's library."""
7
8
9import argparse
10import os
11import re
12import subprocess
13import sys
14
15from util import build_utils
16
17_DUMP_STATIC_INITIALIZERS_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT,
18                                              'tools', 'linux',
19                                              'dump-static-initializers.py')
20
21
22def _RunReadelf(so_path, options, tool_prefix=''):
23  return subprocess.check_output(
24      [tool_prefix + 'readobj', '--elf-output-style=GNU'] + options +
25      [so_path]).decode('utf8')
26
27
28def _DumpStaticInitializers(so_path):
29  subprocess.check_call([_DUMP_STATIC_INITIALIZERS_PATH, so_path])
30
31
32def _ReadInitArray(so_path, tool_prefix):
33  stdout = _RunReadelf(so_path, ['-SW'], tool_prefix)
34  # Matches: .init_array INIT_ARRAY 000000000516add0 5169dd0 000010 00 WA 0 0 8
35  match = re.search(r'\.init_array.*$', stdout, re.MULTILINE)
36  if not match:
37    raise Exception('Did not find section: .init_array in {}:\n{}'.format(
38        so_path, stdout))
39  size_str = re.split(r'\W+', match.group(0))[5]
40  return int(size_str, 16)
41
42
43def _CountStaticInitializers(so_path, tool_prefix):
44  # Find the number of files with at least one static initializer.
45  # First determine if we're 32 or 64 bit
46  stdout = _RunReadelf(so_path, ['-h'], tool_prefix)
47  elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0)
48  elf_class = re.split(r'\W+', elf_class_line)[1]
49  if elf_class == 'ELF32':
50    word_size = 4
51  else:
52    word_size = 8
53
54  # Then find the number of files with global static initializers.
55  # NOTE: this is very implementation-specific and makes assumptions
56  # about how compiler and linker implement global static initializers.
57  init_array_size = _ReadInitArray(so_path, tool_prefix)
58  assert init_array_size % word_size == 0
59  return init_array_size // word_size
60
61
62def main():
63  parser = argparse.ArgumentParser()
64  parser.add_argument('--touch', help='File to touch upon success')
65  parser.add_argument('--tool-prefix', required=True,
66                      help='Prefix for nm and friends')
67  parser.add_argument('--expected-count', required=True, type=int,
68                      help='Fail if number of static initializers is not '
69                           'equal to this value.')
70  parser.add_argument('--unstripped-so-path',
71                      help='Path to the unstripped version of the .so '
72                      'file if needed for better dumps.')
73  parser.add_argument('so_path', help='Path to .so file.')
74  args = parser.parse_args()
75
76  si_count = _CountStaticInitializers(args.so_path, args.tool_prefix)
77  if si_count != args.expected_count:
78    print('Expected {} static initializers, but found {}.'.format(
79        args.expected_count, si_count))
80    if args.expected_count > si_count:
81      print('You have removed one or more static initializers. Thanks!')
82      print('To fix the build, update the expectation in:')
83      print('    //chrome/android/static_initializers.gni')
84      print()
85
86    print('Dumping static initializers via dump-static-initializers.py:')
87    sys.stdout.flush()
88    dump_so_path = args.so_path
89    if args.unstripped_so_path:
90      dump_so_path = args.unstripped_so_path
91    _DumpStaticInitializers(dump_so_path)
92    print()
93    print('For more information:')
94    print('    https://chromium.googlesource.com/chromium/src/+/main/docs/'
95          'static_initializers.md')
96    sys.exit(1)
97
98  if args.touch:
99    open(args.touch, 'w')
100
101
102if __name__ == '__main__':
103  main()
104