diff options
| author | 2019-01-29 20:12:15 +0000 | |
|---|---|---|
| committer | 2019-01-29 20:12:15 +0000 | |
| commit | cdd71cf257997c0e99f6e3414fbb2dab9d51dbd5 (patch) | |
| tree | 59336a0dbf6b8b04e96ad2bc9cc940002b8e62bc | |
| parent | f6eb989e8552b83d8d734d6b90536ec0bcd06bab (diff) | |
| parent | a6852d3ef881bf38db113d320d0ff74544d1136d (diff) | |
Merge "ART: Add python+debugfs based ART APEX checker"
| -rw-r--r-- | Android.mk | 4 | ||||
| -rw-r--r-- | build/apex/Android.bp | 58 | ||||
| -rwxr-xr-x | build/apex/art_apex_test.py | 372 |
3 files changed, 433 insertions, 1 deletions
diff --git a/Android.mk b/Android.mk index 1a5daffd84..b0a918c760 100644 --- a/Android.mk +++ b/Android.mk @@ -352,13 +352,15 @@ ifeq (true,$(art_target_include_debug_build)) # Module with both release and debug variants, as well as # additional tools. TARGET_RUNTIME_APEX := com.android.runtime.debug + APEX_TEST_MODULE := art-check-debug-apex-gen-fakelib else # Release module (without debug variants nor tools). TARGET_RUNTIME_APEX := com.android.runtime.release + APEX_TEST_MODULE := art-check-release-apex-gen-fakelib endif LOCAL_MODULE := com.android.runtime -LOCAL_REQUIRED_MODULES := $(TARGET_RUNTIME_APEX) +LOCAL_REQUIRED_MODULES := $(TARGET_RUNTIME_APEX) $(APEX_TEST_MODULE) # Clear locally used variable. art_target_include_debug_build := diff --git a/build/apex/Android.bp b/build/apex/Android.bp index 9b5c638b67..b1c80cfdd6 100644 --- a/build/apex/Android.bp +++ b/build/apex/Android.bp @@ -219,3 +219,61 @@ art_apex { }, }, } + +python_binary_host { + name: "art-apex-tester", + srcs: ["art_apex_test.py"], + main: "art_apex_test.py", + version: { + py3: { + enabled: true, + }, + }, +} + +// Genrules so we can run the checker, and empty Java library so that it gets executed. + +java_genrule_host { + name: "art-check-release-apex-gen", + srcs: [":com.android.runtime.release"], + tools: [ + "art-apex-tester", + "debugfs", + "soong_zip", + ], + cmd: "$(location art-apex-tester)" + + " --debugfs $(location debugfs)" + + " --tmpdir $(genDir)" + + " --target" + + " $(in)" + + " && $(location soong_zip) -o $(out)", + out: ["art-check-release-apex-gen.srcjar"], +} +java_library_host { + name: "art-check-release-apex-gen-fakelib", + srcs: [":art-check-release-apex-gen"], + installable: false, +} + +java_genrule_host { + name: "art-check-debug-apex-gen", + srcs: [":com.android.runtime.debug"], + tools: [ + "art-apex-tester", + "debugfs", + "soong_zip", + ], + cmd: "$(location art-apex-tester)" + + " --debugfs $(location debugfs)" + + " --tmpdir $(genDir)" + + " --target" + + " --debug" + + " $(in)" + + " && $(location soong_zip) -o $(out)", + out: ["art-check-debug-apex-gen.srcjar"], +} +java_library_host { + name: "art-check-debug-apex-gen-fakelib", + srcs: [":art-check-debug-apex-gen"], + installable: false, +} diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py new file mode 100755 index 0000000000..5ac2f724db --- /dev/null +++ b/build/apex/art_apex_test.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2019 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. +# + +import argparse +import logging +import os +import subprocess +import sys +import zipfile + +logging.basicConfig(format='%(message)s') + +class FSObject: + def __init__(self, name, is_dir, is_exec, is_symlink): + self.name = name + self.is_dir = is_dir + self.is_exec = is_exec + self.is_symlink = is_symlink + def __str__(self): + return '%s(dir=%r,exec=%r,symlink=%r)' % (self.name, self.is_dir, self.is_exec, self.is_symlink) + +class TargetApexProvider: + def __init__(self, apex, tmpdir, debugfs): + self._tmpdir = tmpdir + self._debugfs = debugfs + self._folder_cache = {} + self._payload = os.path.join(self._tmpdir, 'apex_payload.img') + # Extract payload to tmpdir. + zip = zipfile.ZipFile(apex) + zip.extract('apex_payload.img', tmpdir) + + def __del__(self): + # Delete temps. + if os.path.exists(self._payload): + os.remove(self._payload) + + def get(self, path): + dir, name = os.path.split(path) + if len(dir) == 0: + dir = '/' + map = self.read_dir(dir) + return map[name] if name in map else None + + def read_dir(self, dir): + if dir in self._folder_cache: + return self._folder_cache[dir] + # Cannot use check_output as it will annoy with stderr. + process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % (dir), self._payload], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = process.communicate() + res = str(stdout) + map = {} + # Debugfs output looks like this: + # debugfs 1.44.4 (18-Aug-2018) + # /12/040755/0/2000/.// + # /2/040755/1000/1000/..// + # /13/100755/0/2000/dalvikvm32/28456/ + # /14/100755/0/2000/dexoptanalyzer/20396/ + # /15/100755/0/2000/linker/1152724/ + # /16/100755/0/2000/dex2oat/563508/ + # /17/100755/0/2000/linker64/1605424/ + # /18/100755/0/2000/profman/85304/ + # /19/100755/0/2000/dalvikvm64/28576/ + # | | | | | | + # | | | #- gid #- name #- size + # | | #- uid + # | #- type and permission bits + # #- inode nr (?) + # + # Note: could break just on '/' to avoid names with newlines. + for line in res.split("\n"): + if not line: + continue + comps = line.split('/') + if len(comps) != 8: + logging.warn('Could not break and parse line \'%s\'', line) + continue + bits = comps[2] + name = comps[5] + if len(bits) != 6: + logging.warn('Dont understand bits \'%s\'', bits) + continue + is_dir = True if bits[1] == '4' else False + def is_exec_bit(ch): + return True if int(ch) & 1 == 1 else False + is_exec = is_exec_bit(bits[3]) and is_exec_bit(bits[4]) and is_exec_bit(bits[5]) + # TODO: Figure out how this is represented + is_symlink = False + map[name] = FSObject(name, is_dir, is_exec, is_symlink) + self._folder_cache[dir] = map + return map + +class Checker: + def __init__(self, provider): + self._provider = provider + self._errors = 0 + self._is_multilib = provider.get('lib64') is not None; + + def fail(self, msg, *args): + self._errors += 1 + logging.error(msg, args) + + def error_count(self): + return self._errors + + def check_file(self, file): + fs_object = self._provider.get(file) + if fs_object is None: + self.fail('Could not find %s', file) + return False + if fs_object.is_dir: + self.fail('%s is a directory', file) + return False + return True + + def check_binary(self, file): + path = 'bin/%s' % (file) + if not self.check_file(path): + return False + if not self._provider.get(path).is_exec: + self.fail('%s is not executable', path) + return False + return True + + def check_multilib_binary(self, file): + res = self.check_binary('%s32' % (file)) + if self._is_multilib: + res = self.check_binary('%s64' % (file)) and res + return res + + def check_binary_symlink(self, file): + path = 'bin/%s' % (file) + fs_object = self._provider.get(path) + if fs_object is None: + self.fail('Could not find %s', path) + return False + if fs_object.is_dir: + self.fail('%s is a directory', path) + return False + if not fs_object.is_symlink: + self.fail('%s is not a symlink', path) + return False + return True + + def check_library(self, file): + # TODO: Use $TARGET_ARCH (e.g. check whether it is "arm" or "arm64") to improve + # the precision of this test? + res = self.check_file('lib/%s' % (file)) + if self._is_multilib: + res = self.check_file('lib64/%s' % (file)) and res + return res + + def check_java_library(self, file): + return self.check_file('javalib/%s' % (file)) + +class ReleaseChecker(Checker): + def __init__(self, provider): + super().__init__(provider) + def __str__(self): + return 'Release Checker' + + def run(self): + # Check that the mounted image contains an APEX manifest. + self.check_file('apex_manifest.json') + + # Check that the mounted image contains ART base binaries. + self.check_multilib_binary('dalvikvm') + # TODO: Does not work yet (b/119942078). + # self.check_binary_symlink('dalvikvm') + self.check_binary('dex2oat') + self.check_binary('dexoptanalyzer') + self.check_binary('profman') + + # oatdump is only in device apex's due to build rules + # TODO: Check for it when it is also built for host. + # self.check_binary('oatdump') + + # Check that the mounted image contains Android Runtime libraries. + self.check_library('libart-compiler.so') + self.check_library('libart-dexlayout.so') + self.check_library('libart.so') + self.check_library('libartbase.so') + self.check_library('libdexfile.so') + self.check_library('libopenjdkjvm.so') + self.check_library('libopenjdkjvmti.so') + self.check_library('libprofile.so') + # Check that the mounted image contains Android Core libraries. + self.check_library('libexpat.so') + self.check_library('libjavacore.so') + self.check_library('libopenjdk.so') + self.check_library('libz.so') + self.check_library('libziparchive.so') + # Check that the mounted image contains additional required libraries. + self.check_library('libadbconnection.so') + + # TODO: Should we check for other libraries, such as: + # + # libbacktrace.so + # libbase.so + # liblog.so + # libsigchain.so + # libtombstoned_client.so + # libunwindstack.so + # libvixl.so + # libvixld.so + # ... + # + # ? + + self.check_java_library('core-oj.jar') + self.check_java_library('core-libart.jar') + self.check_java_library('okhttp.jar') + self.check_java_library('bouncycastle.jar') + self.check_java_library('apache-xml.jar') + +class DebugChecker(Checker): + def __init__(self, provider): + super().__init__(provider) + def __str__(self): + return 'Debug Checker' + + def run(self): + # Check that the mounted image contains ART tools binaries. + self.check_binary('dexdiag') + self.check_binary('dexdump') + self.check_binary('dexlist') + + # Check that the mounted image contains ART debug binaries. + # TODO(b/123427238): This should probably be dex2oatd, fix! + self.check_binary('dex2oatd32') + self.check_binary('dexoptanalyzerd') + self.check_binary('profmand') + + # Check that the mounted image contains Android Runtime debug libraries. + self.check_library('libartbased.so') + self.check_library('libartd-compiler.so') + self.check_library('libartd-dexlayout.so') + self.check_library('libartd.so') + self.check_library('libdexfiled.so') + self.check_library('libopenjdkjvmd.so') + self.check_library('libopenjdkjvmtid.so') + self.check_library('libprofiled.so') + # Check that the mounted image contains Android Core debug libraries. + self.check_library('libopenjdkd.so') + # Check that the mounted image contains additional required debug libraries. + self.check_library('libadbconnectiond.so') + +# Note: do not sys.exit early, for __del__ cleanup. +def artApexTestMain(args): + if not args.host and not args.target and not args.debug: + logging.error("None of --host, --target nor --debug set") + return 1 + if args.host and (args.target or args.debug): + logging.error("Both of --host and --target|--debug set") + return 1 + if args.debug and not args.target: + args.target = True + if args.target and not args.tmpdir: + logging.error("Need a tmpdir.") + return 1 + if args.target and not args.debugfs: + logging.error("Need debugfs.") + return 1 + + try: + apex_provider = TargetApexProvider(args.apex, args.tmpdir, args.debugfs) + except: + logging.error('Failed to create provider') + return 1 + + checkers = [] + if args.host: + logging.error('host checking not yet supported') + return 1 + + checkers.append(ReleaseChecker(apex_provider)) + if args.debug: + checkers.append(DebugChecker(apex_provider)) + + failed = False + for checker in checkers: + logging.info('%s...', checker) + checker.run() + if checker.error_count() > 0: + logging.error('%s FAILED', checker) + failed = True + else: + logging.info('%s SUCCEEDED', checker) + + return 1 if failed else 0 + +def artApexTestDefault(parser): + if not 'ANDROID_PRODUCT_OUT' in os.environ: + logging.error('No-argument use requires ANDROID_PRODUCT_OUT') + sys.exit(1) + product_out = os.environ['ANDROID_PRODUCT_OUT'] + if not 'ANDROID_HOST_OUT' in os.environ: + logging.error('No-argument use requires ANDROID_HOST_OUT') + sys.exit(1) + host_out = os.environ['ANDROID_HOST_OUT'] + + args = parser.parse_args(['dummy']) # For consistency. + args.debugfs = '%s/bin/debugfs' % (host_out) + args.tmpdir = '.' + failed = False + + if not os.path.exists(args.debugfs): + logging.error("Cannot find debugfs (default path %s). Please build it, e.g., m debugfs", + args.debugfs) + sys.exit(1) + + # TODO: Add host support + configs= [ + {'name': 'com.android.runtime.release', 'target': True, 'debug': False, 'host': False}, + {'name': 'com.android.runtime.debug', 'target': True, 'debug': True, 'host': False}, + ] + + for config in configs: + logging.info(config['name']) + # TODO: Host will need different path. + args.apex = '%s/system/apex/%s.apex' % (product_out, config['name']) + if not os.path.exists(args.apex): + failed = True + logging.error("Cannot find APEX %s. Please build it first.", args.apex) + continue + args.target = config['target'] + args.debug = config['debug'] + args.host = config['host'] + exit_code = artApexTestMain(args) + if exit_code != 0: + failed = True + + if failed: + sys.exit(1) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Check integrity of a Runtime APEX.') + + parser.add_argument('apex', help='apex file input') + + parser.add_argument('--host', help='Check as host apex', action='store_true') + parser.add_argument('--target', help='Check as target apex', action='store_true') + parser.add_argument('--debug', help='Check as debug apex', action='store_true') + + parser.add_argument('--tmpdir', help='Directory for temp files') + parser.add_argument('--debugfs', help='Path to debugfs') + + if len(sys.argv) == 1: + artApexTestDefault(parser) + else: + args = parser.parse_args() + + if args is None: + sys.exit(1) + + exit_code = artApexTestMain(args) + sys.exit(exit_code) |