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