summaryrefslogtreecommitdiff
path: root/deps/v8/build/android/gyp/assert_static_initializers.py
blob: 019baface15160b85c5ee766d84beb5a6df31b16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Checks the number of static initializers in an APK's library."""

import argparse
import os
import re
import subprocess
import sys
import tempfile
import zipfile

from util import build_utils

_DUMP_STATIC_INITIALIZERS_PATH = os.path.join(build_utils.DIR_SOURCE_ROOT,
                                              'tools', 'linux',
                                              'dump-static-initializers.py')


def _RunReadelf(so_path, options, tool_prefix=''):
  return subprocess.check_output([tool_prefix + 'readelf'] + options +
                                 [so_path])


def _ParseLibBuildId(so_path, tool_prefix):
  """Returns the Build ID of the given native library."""
  stdout = _RunReadelf(so_path, ['-n'], tool_prefix)
  match = re.search(r'Build ID: (\w+)', stdout)
  return match.group(1) if match else None


def _VerifyLibBuildIdsMatch(tool_prefix, *so_files):
  if len(set(_ParseLibBuildId(f, tool_prefix) for f in so_files)) > 1:
    raise Exception('Found differing build ids in output directory and apk. '
                    'Your output directory is likely stale.')


def _GetStaticInitializers(so_path, tool_prefix):
  output = subprocess.check_output(
      [_DUMP_STATIC_INITIALIZERS_PATH, '-d', so_path, '-t', tool_prefix])
  summary = re.search(r'Found \d+ static initializers in (\d+) files.', output)
  return output.splitlines()[:-1], int(summary.group(1))


def _PrintDumpSIsCount(apk_so_name, unzipped_so, out_dir, tool_prefix):
  lib_name = os.path.basename(apk_so_name).replace('crazy.', '')
  so_with_symbols_path = os.path.join(out_dir, 'lib.unstripped', lib_name)
  if not os.path.exists(so_with_symbols_path):
    raise Exception('Unstripped .so not found. Looked here: %s',
                    so_with_symbols_path)
  _VerifyLibBuildIdsMatch(tool_prefix, unzipped_so, so_with_symbols_path)
  sis, _ = _GetStaticInitializers(so_with_symbols_path, tool_prefix)
  for si in sis:
    print si


# Mostly copied from //infra/scripts/legacy/scripts/slave/chromium/sizes.py.
def _ReadInitArray(so_path, tool_prefix):
  stdout = _RunReadelf(so_path, ['-SW'], tool_prefix)
  # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8
  match = re.search(r'\.init_array.*$', stdout, re.MULTILINE)
  if not match:
    raise Exception('Did not find section: .init_array in:\n' + stdout)
  size_str = re.split(r'\W+', match.group(0))[5]
  return int(size_str, 16)


def _CountStaticInitializers(so_path, tool_prefix):
  # Find the number of files with at least one static initializer.
  # First determine if we're 32 or 64 bit
  stdout = _RunReadelf(so_path, ['-h'], tool_prefix)
  elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0)
  elf_class = re.split(r'\W+', elf_class_line)[1]
  if elf_class == 'ELF32':
    word_size = 4
  else:
    word_size = 8

  # Then find the number of files with global static initializers.
  # NOTE: this is very implementation-specific and makes assumptions
  # about how compiler and linker implement global static initializers.
  init_array_size = _ReadInitArray(so_path, tool_prefix)
  return init_array_size / word_size


def _AnalyzeStaticInitializers(apk_filename, tool_prefix, dump_sis, out_dir,
                               ignored_libs):
  # Static initializer counting mostly copies logic in
  # infra/scripts/legacy/scripts/slave/chromium/sizes.py.
  with zipfile.ZipFile(apk_filename) as z:
    so_files = [
        f for f in z.infolist() if f.filename.endswith('.so')
        and f.file_size > 0 and os.path.basename(f.filename) not in ignored_libs
    ]
    # Skip checking static initializers for secondary abi libs. They will be
    # checked by 32-bit bots. This avoids the complexity of finding 32 bit .so
    # files in the output directory in 64 bit builds.
    has_64 = any('64' in f.filename for f in so_files)
    files_to_check = [f for f in so_files if not has_64 or '64' in f.filename]

    si_count = 0
    for f in files_to_check:
      with tempfile.NamedTemporaryFile() as temp:
        temp.write(z.read(f))
        temp.flush()
        si_count += _CountStaticInitializers(temp.name, tool_prefix)
        if dump_sis:
          # Print count and list of SIs reported by dump-static-initializers.py.
          # Doesn't work well on all archs (particularly arm), which is why
          # the readelf method is used for tracking SI counts.
          _PrintDumpSIsCount(f.filename, temp.name, out_dir, tool_prefix)
  return si_count


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('--touch', help='File to touch upon success')
  parser.add_argument('--tool-prefix', required=True,
                      help='Prefix for nm and friends')
  parser.add_argument('--expected-count', required=True, type=int,
                      help='Fail if number of static initializers is not '
                           'equal to this value.')
  parser.add_argument('apk', help='APK file path.')
  args = parser.parse_args()

  #TODO(crbug.com/838414): add support for files included via loadable_modules.
  ignored_libs = ['libarcore_sdk_c.so']

  si_count = _AnalyzeStaticInitializers(args.apk, args.tool_prefix, False, '.',
                                        ignored_libs)
  if si_count != args.expected_count:
    print 'Expected {} static initializers, but found {}.'.format(
        args.expected_count, si_count)
    if args.expected_count > si_count:
      print 'You have removed one or more static initializers. Thanks!'
      print 'To fix the build, update the expectation in:'
      print '    //chrome/android/static_initializers.gni'
    else:
      print 'Dumping static initializers via dump-static-initializers.py:'
      sys.stdout.flush()
      _AnalyzeStaticInitializers(args.apk, args.tool_prefix, True, '.',
                                 ignored_libs)
      print
      print 'If the above list is not useful, consider listing them with:'
      print '    //tools/binary_size/diagnose_bloat.py'
      print
      print 'For more information:'
      print ('    https://chromium.googlesource.com/chromium/src/+/master/docs/'
             'static_initializers.md')
    sys.exit(1)

  if args.touch:
    open(args.touch, 'w')


if __name__ == '__main__':
  main()