diff --git a/vndk/tools/Android.bp b/vndk/tools/Android.bp index d008503fa..9ba43917d 100644 --- a/vndk/tools/Android.bp +++ b/vndk/tools/Android.bp @@ -15,6 +15,6 @@ // subdirs = [ - "abides", "header-checker", + "vtable-dumper", ] diff --git a/vndk/tools/abi-tool/README.md b/vndk/tools/abi-tool/README.md new file mode 100644 index 000000000..033bb72e7 --- /dev/null +++ b/vndk/tools/abi-tool/README.md @@ -0,0 +1,36 @@ +VNDK ABI Tool +============= + +This is the wrapper command to collect ABI reference dumps from the binaries +with debug information. + + +## Usage + +First, lunch the product target: + + $ cd ${AOSP_DIR} + $ source build/envsetup.sh + $ lunch ${YOUR_TARGET_NAME} + +Second, build `vndk-vtable-dumper`: + + $ croot + $ cd development/vndk/tools/vtable-dumper + $ mm -j${NUM_CORES} + +Third, run `vndk_abi_tool.py` with VNDK library list file: + + $ croot + $ cd development/vndk/tools/abi-tool + $ ./vndk_abi_tool.py --vndk-list=${VNDK_LIBRARY_LIST_FILE} + +The content of `${VNDK_LIBRARY_LIST_FILE}` should contain VNDK library names +(one name per line.) For example, if the VNDK library set contains +`libjpeg.so` and `libpng.so`, then `${VNDK_LIBRARY_LIST_FILE}` will be: + + libjpeg.so + libpng.so + +You can skip `--vndk-list` as well. In that case, `vndk_abi_tool.py` will +generate ABI dumps for all shared libraries. diff --git a/vndk/tools/abi-tool/vndk_abi_tool.py b/vndk/tools/abi-tool/vndk_abi_tool.py new file mode 100755 index 000000000..c42fc6a30 --- /dev/null +++ b/vndk/tools/abi-tool/vndk_abi_tool.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 + +from __future__ import print_function + +import argparse +import os +import re +import subprocess +import sys +import traceback + +# Python 2 and 3 compatibility layers. +if sys.version_info >= (3, 0): + from os import makedirs + from shutil import which + + def get_byte(buf, idx): + return buf[idx] + + def check_silent_call(cmd): + subprocess.check_call(cmd, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) +else: + def makedirs(path, exist_ok): + if exist_ok and os.path.isdir(path): + return + return os.makedirs(path) + + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + def is_executable(path): + return (os.path.exists(file_path) and \ + os.access(file_path, mode) and \ + not os.path.isdir(file_path)) + if path is None: + path = os.environ.get('PATH', os.defpath) + for path_dir in path.split(os.pathsep): + for file_name in os.listdir(path_dir): + if file_name != cmd: + continue + file_path = os.path.join(path_dir, file_name) + if is_executable(file_path): + return file_path + return None + + def get_byte(buf, idx): + return ord(buf[idx]) + + def check_silent_call(cmd): + with open(os.devnull, 'wb') as devnull: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + + FileNotFoundError = OSError + + +# Path constants. +SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) +AOSP_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, *['..'] * 4)) +ABI_DUMPER = os.path.join(AOSP_DIR, 'external', 'abi-dumper', 'abi-dumper.pl') +VTABLE_DUMPER = 'vndk-vtable-dumper' +BINARY_ABI_DUMP_EXT = '.bdump' + + +# Compilation targets. +class Target(object): + def __init__(self, arch, gcc_arch, gcc_prefix, gcc_version, lib_dir_name): + self.arch = arch + self.gcc_dir = self._get_prebuilts_gcc(gcc_arch, gcc_prefix, + gcc_version) + self.gcc_prefix = gcc_prefix + self.lib_dir_name = lib_dir_name + + def _get_prebuilts_host(self): + """Get the host dir for prebuilts""" + if sys.platform.startswith('linux'): + return 'linux-x86' + if sys.platform.startswith('darwin'): + return 'darwin-x86' + raise NotImplementedError('unknown platform') + + def _get_prebuilts_gcc(self, gcc_arch, gcc_prefix, gcc_version): + """Get the path to gcc for the current platform""" + return os.path.join(AOSP_DIR, 'prebuilts', 'gcc', + self._get_prebuilts_host(), gcc_arch, + gcc_prefix + gcc_version) + + def get_exe(self, name): + """Get the path to prebuilt executable""" + return os.path.join(self.gcc_dir, 'bin', self.gcc_prefix + name) + +class TargetRegistry(object): + def __init__(self): + self.targets = dict() + + def add(self, arch, gcc_arch, gcc_prefix, gcc_version, lib_dir_name): + self.targets[arch] = Target(arch, gcc_arch, gcc_prefix, gcc_version, + lib_dir_name) + + def get(self, arch_name, var_name): + try: + return self.targets[arch_name] + except KeyError: + print('{}: error: unknown {}: {}' + .format(sys.argv[0], var_name, arch_name), file=sys.stderr) + sys.exit(1) + + @staticmethod + def create(): + res = TargetRegistry() + res.add('arm', 'arm', 'arm-linux-androideabi-', '4.9', 'lib') + res.add('arm64', 'aarch64', 'aarch64-linux-android-', '4.9', 'lib64') + res.add('mips', 'mips', 'mips64el-linux-android-', '4.9', 'lib') + res.add('mips64', 'mips', 'mips64el-linux-android-', '4.9', 'lib64') + res.add('x86', 'x86', 'x86_64-linux-android-', '4.9', 'lib') + res.add('x86_64', 'x86', 'x86_64-linux-android-', '4.9', 'lib64') + return res + + +# Command tests. +def test_command(name, options, expected_output): + def is_command_valid(): + try: + if os.path.exists(name) and os.access(name, os.F_OK | os.X_OK): + exec_path = name + else: + exec_path = which(name) + if not exec_path: + return False + output = subprocess.check_output([exec_path] + options) + return (expected_output in output) + except Exception: + traceback.print_exc() + return False + + if not is_command_valid(): + print('error: failed to run {} command'.format(name), file=sys.stderr) + sys.exit(1) + +def test_readelf_command(readelf): + test_command(readelf, ['-v'], b'GNU readelf') + +def test_objdump_command(objdump): + test_command(objdump, ['-v'], b'GNU objdump') + +def test_vtable_dumper_command(): + test_command(VTABLE_DUMPER, ['--version'], b'vndk-vtable-dumper') + +def test_abi_dumper_command(): + test_command(ABI_DUMPER, ['-v'], b'ABI Dumper') + +def test_all_commands(readelf, objdump): + test_readelf_command(readelf) + test_objdump_command(objdump) + test_vtable_dumper_command() + test_abi_dumper_command() + + +# ELF file format constants. +ELF_MAGIC = b'\x7fELF' + +EI_CLASS = 4 +EI_DATA = 5 +EI_NIDENT = 8 + +ELFCLASS32 = 1 +ELFCLASS64 = 2 + +ELFDATA2LSB = 1 +ELFDATA2MSB = 2 + + +# ELF file check utilities. +def is_elf_ident(buf): + # Check the length of ELF ident. + if len(buf) != EI_NIDENT: + return False + + # Check ELF magic word. + if buf[0:4] != ELF_MAGIC: + return False + + # Check ELF machine word size. + ei_class = get_byte(buf, EI_CLASS) + if ei_class != ELFCLASS32 and ei_class != ELFCLASS64: + return False + + # Check ELF endianness. + ei_data = get_byte(buf, EI_DATA) + if ei_data != ELFDATA2LSB and ei_data != ELFDATA2MSB: + return False + + return True + +def is_elf_file(path): + try: + with open(path, 'rb') as f: + return is_elf_ident(f.read(EI_NIDENT)) + except FileNotFoundError: + return False + +def create_vndk_lib_name_filter(file_list_path): + if not file_list_path: + def accept_all_filenames(name): + return True + return accept_all_filenames + + with open(file_list_path, 'r') as f: + lines = f.read().splitlines() + + patt = re.compile('^(?:' + + '|'.join('(?:' + re.escape(x) + ')' for x in lines) + + ')$') + def accept_matched_filenames(name): + return patt.match(name) + return accept_matched_filenames + +def create_abi_reference_dump(out_dir, symbols_dir, api_level, show_commands, + target, is_vndk_lib_name): + # Check command line tools. + readelf = target.get_exe('readelf') + objdump = target.get_exe('objdump') + test_all_commands(readelf, objdump) + + # Check library directory. + lib_dir = os.path.join(symbols_dir, 'system', target.lib_dir_name) + if not os.path.exists(lib_dir): + print('error: failed to find lib directory:', lib_dir, file=sys.stderr) + sys.exit(1) + + # Append target architecture to output directory path. + out_dir = os.path.join(out_dir, target.arch) + + # Process libraries. + cmd_base = [ABI_DUMPER, '-lver', api_level, '-objdump', objdump, + '-readelf', readelf, '-vt-dumper', which(VTABLE_DUMPER), + '-use-tu-dump', '--quiet'] + + num_processed = 0 + lib_dir = os.path.abspath(lib_dir) + prefix_len = len(lib_dir) + 1 + for base, dirnames, filenames in os.walk(lib_dir): + for filename in filenames: + if not is_vndk_lib_name(filename): + continue + + path = os.path.join(base, filename) + if not is_elf_file(path): + continue + + rel_path = path[prefix_len:] + out_path = os.path.join(out_dir, rel_path) + BINARY_ABI_DUMP_EXT + + makedirs(os.path.dirname(out_path), exist_ok=True) + cmd = cmd_base + [path, '-o', out_path] + if show_commands: + print('run:', ' '.join(cmd)) + else: + print('process:', path) + check_silent_call(cmd) + num_processed += 1 + + return num_processed + +def get_build_var_from_build_system(name): + """Get build system variable for the launched target.""" + if 'ANDROID_PRODUCT_OUT' not in os.environ: + return None + + cmd = ['make', '--no-print-directory', '-f', 'build/core/config.mk', + 'dumpvar-' + name] + + environ = dict(os.environ) + environ['CALLED_FROM_SETUP'] = 'true' + environ['BUILD_SYSTEM'] = 'build/core' + + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=environ, + cwd=AOSP_DIR) + out, err = proc.communicate() + return out.decode('utf-8').strip() + +def get_build_var(name, args): + """Get build system variable either from command line option or build + system.""" + value = getattr(args, name.lower(), None) + return value if value else get_build_var_from_build_system(name) + +def report_missing_argument(parser, arg_name): + parser.print_usage() + print('{}: error: the following arguments are required: {}' + .format(sys.argv[0], arg_name), file=sys.stderr) + sys.exit(1) + +def main(): + # Parse command line options. + parser = argparse.ArgumentParser() + parser.add_argument('--output', '-o', metavar='path', + help='output directory for abi reference dump') + parser.add_argument('--vndk-list', help='VNDK library list') + parser.add_argument('--api-level', default='24', help='VNDK API level') + parser.add_argument('--target-arch', help='target architecture') + parser.add_argument('--target-2nd-arch', help='second target architecture') + parser.add_argument('--product-out', help='android product out') + parser.add_argument('--target-product', help='target product') + parser.add_argument('--target-build-variant', help='target build variant') + parser.add_argument('--symbols-dir', help='unstripped symbols directory') + parser.add_argument('--show-commands', action='store_true', + help='Show the abi-dumper command') + args = parser.parse_args() + + # Check the symbols directory. + if args.symbols_dir: + symbols_dir = args.symbols_dir + else: + # If the user did not specify the symbols directory, try to create + # one from ANDROID_PRODUCT_OUT. + product_out = get_build_var('PRODUCT_OUT', args) + if not product_out: + report_missing_argument(parser, '--symbols-dir') + if not os.path.isabs(product_out): + product_out = os.path.join(AOSP_DIR, product_out) + symbols_dir = os.path.join(product_out, 'symbols') + + # Check the output directory. + if args.output: + out_dir = args.output + else: + # If the user did not specify the output directory, try to create one + # default output directory from TARGET_PRODUCT and + # TARGET_BUILD_VARIANT. + + target_product = get_build_var('TARGET_PRODUCT', args) + target_build_variant = get_build_var('TARGET_BUILD_VARIANT', args) + if not target_product or not target_build_variant: + report_missing_argument(parser, '--output/-o') + lunch_name = target_product + '-' + target_build_variant + out_dir = os.path.join(AOSP_DIR, 'vndk', 'dumps', lunch_name) + + # Check the targets. + target_registry = TargetRegistry.create() + targets = [] + + arch_name = get_build_var('TARGET_ARCH', args) + if not arch_name: + report_missing_argument(parser, '--target-arch') + targets.append(target_registry.get(arch_name, 'TARGET_ARCH')) + must_have_2nd_arch = (targets[0].lib_dir_name == 'lib64') + + arch_name = get_build_var('TARGET_2ND_ARCH', args) + if arch_name: + targets.append(target_registry.get(arch_name, 'TARGET_2ND_ARCH')) + elif must_have_2nd_arch: + report_missing_argument(parser, '--target-2nd-arch') + + # Dump all libraries for the specified architectures. + num_processed = 0 + for target in targets: + num_processed += create_abi_reference_dump( + out_dir, symbols_dir, args.api_level, args.show_commands, + target, create_vndk_lib_name_filter(args.vndk_list)) + + # Print a summary at the end. + _TERM_WIDTH = 79 + print() + print('-' * _TERM_WIDTH) + print('msg: Reference dump created at directory:', out_dir) + print('msg: Processed', num_processed, 'libraries') + +if __name__ == '__main__': + main() diff --git a/vndk/tools/abides/Android.bp b/vndk/tools/abides/Android.bp deleted file mode 100644 index cf4c24f72..000000000 --- a/vndk/tools/abides/Android.bp +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -subdirs = [ - "vtable", -] diff --git a/vndk/tools/abides/vtable/llvm_vtable_dump.cpp b/vndk/tools/abides/vtable/llvm_vtable_dump.cpp deleted file mode 100644 index fcbf79386..000000000 --- a/vndk/tools/abides/vtable/llvm_vtable_dump.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "llvm_elf_handling.h" - -using llvm::Expected; -using llvm::StringRef; -using llvm::dyn_cast; -using llvm::object::ObjectFile; -using llvm::object::OwningBinary; -using llvm::outs; - -int main (int argc, char **argv) -{ - if (argc != 2) { - outs() << "usage: figure-out-vtables path \n"; - return 1; - } - Expected> Binary = - ObjectFile::createObjectFile(StringRef(argv[1])); - if (!Binary) { - outs() << "Couldn't create object File \n"; - return 1; - } - ObjectFile *Objfile = dyn_cast(&(*Binary.get().getBinary())); - if (!Objfile) { - return 1; - } - auto SoFile = SharedObject::create(Objfile); - if (!SoFile) { - outs() << "Couldn't create ELFObjectFile \n"; - return 1; - } - SoFile->printVTables(); - return 0; -} diff --git a/vndk/tools/abides/vtable/Android.bp b/vndk/tools/vtable-dumper/Android.bp similarity index 80% rename from vndk/tools/abides/vtable/Android.bp rename to vndk/tools/vtable-dumper/Android.bp index 6e0f7956b..dbf052465 100644 --- a/vndk/tools/abides/vtable/Android.bp +++ b/vndk/tools/vtable-dumper/Android.bp @@ -14,15 +14,16 @@ // limitations under the License. // -cc_defaults { - name: "vndk-vtable-defaults", +cc_binary_host { + name: "vndk-vtable-dumper", defaults: [ "clang-defaults", ], - shared_libs: [ - "libLLVM", + srcs: [ + "elf_handling.cpp", + "vndk_vtable_dumper.cpp", ], cflags: [ @@ -30,16 +31,8 @@ cc_defaults { "-Werror", "-std=c++11", ], -} -cc_binary_host { - name: "llvm-figure-out-vtables", - - defaults: [ - "vndk-vtable-defaults" + shared_libs: [ + "libLLVM", ], - - srcs: [ - "llvm_elf_handling.cpp", - "llvm_vtable_dump.cpp"], } diff --git a/vndk/tools/abides/vtable/llvm_elf_handling.cpp b/vndk/tools/vtable-dumper/elf_handling.cpp similarity index 99% rename from vndk/tools/abides/vtable/llvm_elf_handling.cpp rename to vndk/tools/vtable-dumper/elf_handling.cpp index e7adfc3ed..635b09edd 100644 --- a/vndk/tools/abides/vtable/llvm_elf_handling.cpp +++ b/vndk/tools/vtable-dumper/elf_handling.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "llvm_elf_handling.h" +#include "elf_handling.h" #include diff --git a/vndk/tools/abides/vtable/llvm_elf_handling.h b/vndk/tools/vtable-dumper/elf_handling.h similarity index 99% rename from vndk/tools/abides/vtable/llvm_elf_handling.h rename to vndk/tools/vtable-dumper/elf_handling.h index 25ee6cafa..2237c2165 100644 --- a/vndk/tools/abides/vtable/llvm_elf_handling.h +++ b/vndk/tools/vtable-dumper/elf_handling.h @@ -167,4 +167,3 @@ static inline T UnWrap(llvm::Expected ValueOrError) { #endif // ELF_HANDLING_H_ - diff --git a/vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/arm/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/arm/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/arm/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/arm64/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/arm64/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/arm64/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/mips/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/mips/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/mips/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/mips64/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/mips64/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/mips64/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/x86/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/x86/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/x86/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt b/vndk/tools/vtable-dumper/tests/expected/x86_64/libtest.so.txt similarity index 100% rename from vndk/tools/abides/vtable/test/expected/x86_64/libtest.so.txt rename to vndk/tools/vtable-dumper/tests/expected/x86_64/libtest.so.txt diff --git a/vndk/tools/abides/vtable/test/test1.cpp b/vndk/tools/vtable-dumper/tests/test1.cpp similarity index 100% rename from vndk/tools/abides/vtable/test/test1.cpp rename to vndk/tools/vtable-dumper/tests/test1.cpp diff --git a/vndk/tools/abides/vtable/test/test_vtable.py b/vndk/tools/vtable-dumper/tests/test_vndk_vtable_dumper.py similarity index 98% rename from vndk/tools/abides/vtable/test/test_vtable.py rename to vndk/tools/vtable-dumper/tests/test_vndk_vtable_dumper.py index 048e8fac4..37848ee44 100755 --- a/vndk/tools/abides/vtable/test/test_vtable.py +++ b/vndk/tools/vtable-dumper/tests/test_vndk_vtable_dumper.py @@ -28,7 +28,7 @@ NDK_VERSION = 'r11' API_LEVEL = 'android-24' SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -VNDK_VTABLE_DUMP = os.path.join('llvm-figure-out-vtables') +VNDK_VTABLE_DUMPER = 'vndk-vtable-dumper' def get_dirnames(path, n): """Get directory, n directories before path""" @@ -77,7 +77,7 @@ def run_output(cmd, verbose=False): def run_vtable_dump(path, verbose=False): """Run vndk vtable dumper""" - return run_output([VNDK_VTABLE_DUMP, path], verbose) + return run_output([VNDK_VTABLE_DUMPER, path], verbose) class Target(object): @@ -235,7 +235,7 @@ def main(): if args.android_build_top: android_build_top = args.android_build_top else: - android_build_top = get_dirnames(SCRIPT_DIR, 6) + android_build_top = get_dirnames(SCRIPT_DIR, 5) # Find expected output directory. if args.expected_dir: diff --git a/vndk/tools/vtable-dumper/vndk_vtable_dumper.cpp b/vndk/tools/vtable-dumper/vndk_vtable_dumper.cpp new file mode 100644 index 000000000..2ce2b161e --- /dev/null +++ b/vndk/tools/vtable-dumper/vndk_vtable_dumper.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "elf_handling.h" + +#include + +using llvm::Expected; +using llvm::StringMapEntry; +using llvm::cl::Hidden; +using llvm::cl::Option; +using llvm::cl::OptionCategory; +using llvm::cl::ParseCommandLineOptions; +using llvm::cl::Positional; +using llvm::cl::Required; +using llvm::cl::SetVersionPrinter; +using llvm::cl::cat; +using llvm::cl::desc; +using llvm::cl::getRegisteredOptions; +using llvm::cl::opt; +using llvm::dyn_cast; +using llvm::object::ObjectFile; +using llvm::object::OwningBinary; +using llvm::outs; + +OptionCategory VTableDumperCategory("vndk-vtable-dumper options"); + +opt FilePath( + Positional, Required, cat(VTableDumperCategory), + desc("shared_library.so")); + +static void HideIrrelevantCommandLineOptions() { + for (StringMapEntry