Home | History | Annotate | Download | only in golden
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 
     18 import argparse
     19 import importlib
     20 import os
     21 import subprocess
     22 import sys
     23 
     24 
     25 class ExternalModules(object):
     26     """This class imports modules dynamically and keeps them as attributes.
     27 
     28     Assume the user runs this script in the source directory. The VTS modules
     29     are outside the search path and thus have to be imported dynamically.
     30 
     31     Attribtues:
     32         elf_parser: The elf_parser module.
     33         vtable_parser: The vtable_parser module.
     34     """
     35     @classmethod
     36     def ImportParsers(cls, import_dir):
     37         """Imports elf_parser and vtable_parser.
     38 
     39         Args:
     40             import_dir: The directory containing vts.utils.python.library.*.
     41         """
     42         sys.path.append(import_dir)
     43         cls.elf_parser = importlib.import_module(
     44                 "vts.utils.python.library.elf_parser")
     45         cls.vtable_parser = importlib.import_module(
     46                 "vts.utils.python.library.vtable_parser")
     47 
     48 
     49 def GetBuildVariable(build_top_dir, var):
     50     """Gets value of a variable from build config.
     51 
     52     Args:
     53         build_top_dir: The path to root directory of Android source.
     54         var: The name of the variable.
     55 
     56     Returns:
     57         A string which is the value of the variable.
     58     """
     59     build_core = os.path.join(build_top_dir, "build", "core")
     60     env = dict(os.environ)
     61     env["CALLED_FROM_SETUP"] = "true"
     62     env["BUILD_SYSTEM"] = build_core
     63     cmd = ["make", "--no-print-directory",
     64            "-f", os.path.join(build_core, "config.mk"),
     65            "dumpvar-" + var]
     66     proc = subprocess.Popen(cmd, env=env, cwd=build_top_dir,
     67                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     68     stdout, stderr = proc.communicate()
     69     if stderr:
     70         sys.exit("Cannot get variable: cmd=%s\nstdout=%s\nstderr=%s" % (
     71                  cmd, stdout, stderr))
     72     return stdout.strip()
     73 
     74 
     75 def FindBinary(file_name):
     76     """Finds an executable binary in environment variable PATH.
     77 
     78     Args:
     79         file_name: The file name to find.
     80 
     81     Returns:
     82         A string which is the path to the binary.
     83     """
     84     cmd = ["which", file_name]
     85     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     86     stdout, stderr = proc.communicate()
     87     if proc.returncode:
     88         sys.exit("Cannot find file: cmd=%s\nstdout=%s\nstderr=%s" % (
     89                  cmd, stdout, stderr))
     90     return stdout
     91 
     92 
     93 def DumpSymbols(lib_path, dump_path):
     94     """Dump symbols from a library to a dump file.
     95 
     96     The dump file is a sorted list of symbols. Each line contains one symbol.
     97 
     98     Args:
     99         lib_path: The path to the library.
    100         dump_path: The path to the dump file.
    101 
    102     Returns:
    103         A string which is the description about the result.
    104 
    105     Raises:
    106         elf_parser.ElfError if fails to load the library.
    107         IOError if fails to write to the dump.
    108     """
    109     elf_parser = ExternalModules.elf_parser
    110     parser = None
    111     try:
    112         parser = elf_parser.ElfParser(lib_path)
    113         symbols = parser.ListGlobalDynamicSymbols()
    114     finally:
    115         if parser:
    116             parser.Close()
    117     if not symbols:
    118         return "No symbols"
    119     symbols.sort()
    120     with open(dump_path, "w") as dump_file:
    121         dump_file.write("\n".join(symbols) + "\n")
    122     return "Output: " + dump_path
    123 
    124 
    125 def DumpVtables(lib_path, dump_path, dumper_dir):
    126     """Dump vtables from a library to a dump file.
    127 
    128     The dump file is the raw output of vndk-vtable-dumper.
    129 
    130     Args:
    131         lib_path: The path to the library.
    132         dump_path: The path to the text file.
    133         dumper_dir: The path to the directory containing the dumper executable
    134                     and library.
    135 
    136     Returns:
    137         A string which is the description about the result.
    138 
    139     Raises:
    140         vtable_parser.VtableError if fails to load the library.
    141         IOError if fails to write to the dump.
    142     """
    143     vtable_parser = ExternalModules.vtable_parser
    144     parser = vtable_parser.VtableParser(dumper_dir)
    145     vtables = parser.CallVtableDumper(lib_path)
    146     if not vtables:
    147         return "No vtables"
    148     with open(dump_path, "w+") as dump_file:
    149         dump_file.write(vtables)
    150     return "Output: " + dump_path
    151 
    152 
    153 def GetSystemLibDirByArch(product_dir, arch_name):
    154     """Returns the directory containing libraries for specific architecture.
    155 
    156     Args:
    157         product_dir: The path to the product output directory in Android source.
    158         arch_name: The name of the CPU architecture.
    159 
    160     Returns:
    161         The path to the directory containing the libraries.
    162     """
    163     if arch_name in ("arm", "x86", "mips"):
    164         src_dir = os.path.join(product_dir, "system", "lib")
    165     elif arch_name in ("arm64", "x86_64", "mips64"):
    166         src_dir = os.path.join(product_dir, "system", "lib64")
    167     else:
    168         sys.exit("Unknown target arch " + str(target_arch))
    169     return src_dir
    170 
    171 
    172 def DumpAbi(output_dir, input_files, product_dir, archs, dumper_dir):
    173     """Generates dump from libraries.
    174 
    175     Args:
    176         output_dir: The output directory of dump files.
    177         input_files: A list of strings. Each element can be .so file or a text
    178                      file which contains list of libraries.
    179         product_dir: The path to the product output directory in Android source.
    180         archs: A list of strings which are the CPU architectures of the
    181                libraries.
    182         dumper_dir: The path to the directory containing the vtable dumper
    183                     executable and library.
    184     """
    185     # Get names of the libraries to dump
    186     lib_names = []
    187     for input_file in input_files:
    188         if input_file.endswith(".so"):
    189             lib_names.append(input_file)
    190         else:
    191             with open(input_file, "r") as lib_list:
    192                 lib_names.extend(line.strip() for line in lib_list
    193                                  if line.strip())
    194     # Create the dumps
    195     for arch in archs:
    196         lib_dir = GetSystemLibDirByArch(product_dir, arch)
    197         dump_dir = os.path.join(output_dir, arch)
    198         if not os.path.exists(dump_dir):
    199             os.makedirs(dump_dir)
    200         for lib_name in lib_names:
    201             lib_path = os.path.join(lib_dir, lib_name)
    202             symbol_dump_path = os.path.join(dump_dir, lib_name + "_symbol.dump")
    203             vtable_dump_path = os.path.join(dump_dir, lib_name + "_vtable.dump")
    204             print(lib_path)
    205             print(DumpSymbols(lib_path, symbol_dump_path))
    206             print(DumpVtables(lib_path, vtable_dump_path, dumper_dir))
    207             print("")
    208 
    209 
    210 def main():
    211     # Parse arguments
    212     arg_parser = argparse.ArgumentParser()
    213     arg_parser.add_argument("file", nargs="*",
    214                             help="the library to dump. Can be .so file or a"
    215                                  "text file containing list of libraries.")
    216     arg_parser.add_argument("--dumper-dir", "-d", action="store",
    217                             help="the path to the directory containing "
    218                                  "bin/vndk-vtable-dumper.")
    219     arg_parser.add_argument("--import-path", "-i", action="store",
    220                             help="the directory for VTS python modules. "
    221                                  "Default value is $ANDROID_BUILD_TOP/test")
    222     arg_parser.add_argument("--output", "-o", action="store", required=True,
    223                             help="output directory for ABI reference dump.")
    224     args = arg_parser.parse_args()
    225 
    226     # Get product directory
    227     product_dir = os.getenv("ANDROID_PRODUCT_OUT")
    228     if not product_dir:
    229         sys.exit("env var ANDROID_PRODUCT_OUT is not set")
    230     print("ANDROID_PRODUCT_OUT=" + product_dir)
    231 
    232     # Get target architectures
    233     build_top_dir = os.getenv("ANDROID_BUILD_TOP")
    234     if not build_top_dir:
    235         sys.exit("env var ANDROID_BUILD_TOP is not set")
    236     target_arch = GetBuildVariable(build_top_dir, "TARGET_ARCH")
    237     target_2nd_arch = GetBuildVariable(build_top_dir, "TARGET_2ND_ARCH")
    238     print("TARGET_ARCH=" + target_arch)
    239     print("TARGET_2ND_ARCH=" + target_2nd_arch)
    240     archs = [target_arch]
    241     if target_2nd_arch:
    242         archs.append(target_2nd_arch)
    243 
    244     # Import elf_parser and vtable_parser
    245     ExternalModules.ImportParsers(args.import_path if args.import_path else
    246                                   os.path.join(build_top_dir, "test"))
    247 
    248     # Find vtable dumper
    249     if args.dumper_dir:
    250         dumper_dir = args.dumper_dir
    251     else:
    252         dumper_path = FindBinary(vtable_parser.VtableParser.VNDK_VTABLE_DUMPER)
    253         dumper_dir = os.path.dirname(os.path.dirname(dumper_path))
    254     print("DUMPER_DIR=" + dumper_dir)
    255 
    256     DumpAbi(args.output, args.file, product_dir, archs, dumper_dir)
    257 
    258 
    259 if __name__ == "__main__":
    260     main()
    261