diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:22:20 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:22:20 +0000 |
commit | 25a7e2b566d096c3863e968f8cb553a5a862e353 (patch) | |
tree | a26da1d669e88b34b10b0f6c76b06abd8b694473 | |
parent | a640a99bcc56aa9626b60250e6806d7dd5d4cb80 (diff) | |
parent | a19d32b0216a289c888612cdd078679b850d7218 (diff) | |
download | art-android10-mainline-resolv-release.tar.gz |
Snap for 6001391 from a19d32b0216a289c888612cdd078679b850d7218 to qt-aml-resolv-releaseandroid-mainline-10.0.0_r8android10-mainline-resolv-release
Change-Id: I01ad349d71688759e45df341e3827c0af26a590a
180 files changed, 6015 insertions, 1519 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk index 43204fd597..5aff4cfbff 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -97,6 +97,11 @@ $(call add-clean-step, rm -rf $(HOST_OUT)/apex) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/apex) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/apex) +# Remove dex2oat artifacts for boot image extensions (workaround for broken dependencies). +$(call add-clean-step, find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" -o -name '*.vdex' | xargs rm -f) +$(call add-clean-step, find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" -o -name '*.vdex' | xargs rm -f) +$(call add-clean-step, find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" -o -name '*.vdex' | xargs rm -f) + # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/adbconnection/Android.bp b/adbconnection/Android.bp index da73619f00..239cecb2b9 100644 --- a/adbconnection/Android.bp +++ b/adbconnection/Android.bp @@ -55,15 +55,28 @@ art_cc_library { ], } +// We export a library to do the server-side socket handling that gets loaded +// by adbd from the art apex, so that we can update the socket handling +// independently from the adbd apex. cc_library { name: "libadbconnection_server", + srcs: ["adbconnection_server.cc"], + + export_include_dirs: ["include"], + + stl: "libc++_static", + shared_libs: ["liblog"], + whole_static_libs: ["libbase"], + + defaults: ["art_defaults"], visibility: [ - // TODO(b/133140750): Clean this up. "//system/core/adb", ], - srcs: ["adbconnection_server.cc"], - export_include_dirs: ["include"], - shared_libs: ["libbase"], + stubs: { + symbol_file: "libadbconnection_server.map.txt", + versions: ["1"], + }, + host_supported: true, recovery_available: true, } diff --git a/adbconnection/adbconnection_server.cc b/adbconnection/adbconnection_server.cc index 9c1aff913c..f69f4a7196 100644 --- a/adbconnection/adbconnection_server.cc +++ b/adbconnection/adbconnection_server.cc @@ -72,12 +72,12 @@ void adbconnection_listen(void (*callback)(int fd, pid_t pid)) { } while (true) { - int rc = TEMP_FAILURE_RETRY(epoll_wait(epfd.get(), events.data(), events.size(), -1)); - if (rc == -1) { + int epoll_rc = TEMP_FAILURE_RETRY(epoll_wait(epfd.get(), events.data(), events.size(), -1)); + if (epoll_rc == -1) { PLOG(FATAL) << "epoll_wait failed"; } - for (int i = 0; i < rc; ++i) { + for (int i = 0; i < epoll_rc; ++i) { const epoll_event& event = events[i]; if (event.data.fd == -1) { unique_fd client(TEMP_FAILURE_RETRY( diff --git a/adbconnection/libadbconnection_server.map.txt b/adbconnection/libadbconnection_server.map.txt new file mode 100644 index 0000000000..b631581031 --- /dev/null +++ b/adbconnection/libadbconnection_server.map.txt @@ -0,0 +1,22 @@ +# +# 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. +# + +LIBADBCONNECTION_SERVER_1 { + global: + adbconnection_listen; + local: + *; +}; diff --git a/build/Android.bp b/build/Android.bp index e2767ffc24..d686f34668 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -81,7 +81,8 @@ art_global_defaults { // Warn about thread safety violations with clang. "-Wthread-safety", - "-Wthread-safety-negative", + // TODO(b/144045034): turn on -Wthread-safety-negative + //"-Wthread-safety-negative", // Warn if switch fallthroughs aren't annotated. "-Wimplicit-fallthrough", diff --git a/build/Android.oat.mk b/build/Android.oat.mk index c29cd299ea..7c3e791e78 100644 --- a/build/Android.oat.mk +++ b/build/Android.oat.mk @@ -63,6 +63,7 @@ define create-core-oat-host-rules $$(error found $(1) expected interpreter, interp-ac, or optimizing) endif + core_image_location := $(HOST_OUT_JAVA_LIBRARIES)/core$$(core_infix)$(CORE_IMG_SUFFIX) core_image_name := $($(2)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$(CORE_IMG_SUFFIX) core_oat_name := $($(2)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$(CORE_OAT_SUFFIX) @@ -76,18 +77,46 @@ define create-core-oat-host-rules HOST_CORE_OAT_OUTS += $$(core_oat_name) $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options) +$$(core_image_name): PRIVATE_CORE_IMAGE_LOCATION := $$(core_image_location) $$(core_image_name): PRIVATE_CORE_IMG_NAME := $$(core_image_name) $$(core_image_name): PRIVATE_CORE_OAT_NAME := $$(core_oat_name) -$$(core_image_name): $$(HOST_CORE_IMG_DEX_LOCATIONS) $$(core_dex2oat_dependency) +# In addition to the primary core image containing HOST_CORE_IMG_DEX_FILES, +# also build a boot image extension for the remaining HOST_CORE_DEX_FILES. +$$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency) @echo "host dex2oat: $$@" @mkdir -p $$(dir $$@) - $$(hide) ANDROID_LOG_TAGS="*:e" $$(DEX2OAT) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ + $$(hide) ANDROID_LOG_TAGS="*:e" $$(DEX2OAT) \ + --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \ $$(addprefix --dex-file=,$$(HOST_CORE_IMG_DEX_FILES)) \ $$(addprefix --dex-location=,$$(HOST_CORE_IMG_DEX_LOCATIONS)) \ --oat-file=$$(PRIVATE_CORE_OAT_NAME) \ - --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \ - --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(2)ART_HOST_ARCH) \ + --oat-location=$$(PRIVATE_CORE_OAT_NAME) \ + --image=$$(PRIVATE_CORE_IMG_NAME) \ + --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) \ + --instruction-set=$$($(2)ART_HOST_ARCH) \ + $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \ + --host --android-root=$$(HOST_OUT) \ + --generate-debug-info --generate-build-id \ + --runtime-arg -XX:SlowDebug=true \ + --no-inline-from=core-oj-hostdex.jar \ + $$(PRIVATE_CORE_COMPILE_OPTIONS) && \ + ANDROID_LOG_TAGS="*:e" $$(DEX2OAT) \ + --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ + --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \ + --runtime-arg -Xbootclasspath:$$(subst $$(space),:,$$(strip \ + $$(HOST_CORE_DEX_FILES))) \ + --runtime-arg -Xbootclasspath-locations:$$(subst $$(space),:,$$(strip \ + $$(HOST_CORE_DEX_LOCATIONS))) \ + $$(addprefix --dex-file=, \ + $$(filter-out $$(HOST_CORE_IMG_DEX_FILES),$$(HOST_CORE_DEX_FILES))) \ + $$(addprefix --dex-location=, \ + $$(filter-out $$(HOST_CORE_IMG_DEX_LOCATIONS),$$(HOST_CORE_DEX_LOCATIONS))) \ + --oat-file=$$(PRIVATE_CORE_OAT_NAME) \ + --oat-location=$$(PRIVATE_CORE_OAT_NAME) \ + --boot-image=$$(PRIVATE_CORE_IMAGE_LOCATION) \ + --image=$$(PRIVATE_CORE_IMG_NAME) \ + --instruction-set=$$($(2)ART_HOST_ARCH) \ $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \ --host --android-root=$$(HOST_OUT) \ --generate-debug-info --generate-build-id \ @@ -149,6 +178,7 @@ define create-core-oat-target-rules $$(error found $(1) expected interpreter, interp-ac, or optimizing) endif + core_image_location := $(ART_TARGET_TEST_OUT)/core$$(core_infix)$(CORE_IMG_SUFFIX) core_image_name := $($(2)TARGET_CORE_IMG_OUT_BASE)$$(core_infix)$(CORE_IMG_SUFFIX) core_oat_name := $($(2)TARGET_CORE_OAT_OUT_BASE)$$(core_infix)$(CORE_OAT_SUFFIX) @@ -166,24 +196,53 @@ define create-core-oat-target-rules TARGET_CORE_OAT_OUTS += $$(core_oat_name) $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options) +$$(core_image_name): PRIVATE_CORE_IMAGE_LOCATION := $$(core_image_location) $$(core_image_name): PRIVATE_CORE_IMG_NAME := $$(core_image_name) $$(core_image_name): PRIVATE_CORE_OAT_NAME := $$(core_oat_name) -$$(core_image_name): $$(TARGET_CORE_IMG_DEX_FILES) $$(core_dex2oat_dependency) +# In addition to the primary core image containing TARGET_CORE_IMG_DEX_FILES, +# also build a boot image extension for the remaining TARGET_CORE_DEX_FILES. +$$(core_image_name): $$(TARGET_CORE_DEX_FILES) $$(core_dex2oat_dependency) @echo "target dex2oat: $$@" @mkdir -p $$(dir $$@) - $$(hide) $$(DEX2OAT) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ + $$(hide) $$(DEX2OAT) \ + --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \ $$(addprefix --dex-file=,$$(TARGET_CORE_IMG_DEX_FILES)) \ $$(addprefix --dex-location=,$$(TARGET_CORE_IMG_DEX_LOCATIONS)) \ --oat-file=$$(PRIVATE_CORE_OAT_NAME) \ - --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \ - --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(2)TARGET_ARCH) \ + --oat-location=$$(PRIVATE_CORE_OAT_NAME) \ + --image=$$(PRIVATE_CORE_IMG_NAME) \ + --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) \ + --instruction-set=$$($(2)TARGET_ARCH) \ + --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \ + --instruction-set-features=$$($(2)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ + --android-root=$$(PRODUCT_OUT)/system \ + --generate-debug-info --generate-build-id \ + --runtime-arg -XX:SlowDebug=true \ + $$(PRIVATE_CORE_COMPILE_OPTIONS) && \ + $$(DEX2OAT) \ + --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \ + --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \ + --runtime-arg -Xbootclasspath:$$(subst $$(space),:,$$(strip \ + $$(TARGET_CORE_DEX_FILES))) \ + --runtime-arg -Xbootclasspath-locations:$$(subst $$(space),:,$$(strip \ + $$(TARGET_CORE_DEX_LOCATIONS))) \ + $$(addprefix --dex-file=, \ + $$(filter-out $$(TARGET_CORE_IMG_DEX_FILES),$$(TARGET_CORE_DEX_FILES))) \ + $$(addprefix --dex-location=, \ + $$(filter-out $$(TARGET_CORE_IMG_DEX_LOCATIONS),$$(TARGET_CORE_DEX_LOCATIONS))) \ + --oat-file=$$(PRIVATE_CORE_OAT_NAME) \ + --oat-location=$$(PRIVATE_CORE_OAT_NAME) \ + --boot-image=$$(PRIVATE_CORE_IMAGE_LOCATION) \ + --image=$$(PRIVATE_CORE_IMG_NAME) \ + --instruction-set=$$($(2)TARGET_ARCH) \ --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \ --instruction-set-features=$$($(2)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \ --android-root=$$(PRODUCT_OUT)/system \ --generate-debug-info --generate-build-id \ --runtime-arg -XX:SlowDebug=true \ - $$(PRIVATE_CORE_COMPILE_OPTIONS) || (rm $$(PRIVATE_CORE_OAT_NAME); exit 1) + $$(PRIVATE_CORE_COMPILE_OPTIONS) || \ + (rm $$(PRIVATE_CORE_OAT_NAME); exit 1) $$(core_oat_name): $$(core_image_name) diff --git a/build/apex/Android.bp b/build/apex/Android.bp index ceb3093294..809b8fce40 100644 --- a/build/apex/Android.bp +++ b/build/apex/Android.bp @@ -27,6 +27,7 @@ art_runtime_base_binaries_prefer32 = [ // the ART APEX. art_runtime_base_native_shared_libs = [ // External API (having APEX stubs). + "libadbconnection_server", "libdexfile_external", "libnativebridge", "libnativehelper", @@ -228,13 +229,6 @@ apex_defaults { art_tools_device_only_binaries, }, }, - binaries: [ - "art_postinstall_hook", - "art_preinstall_hook", - "art_preinstall_hook_boot", - "art_preinstall_hook_system_server", - "art_prepostinstall_utils", - ], prebuilts: ["com.android.art.ld.config.txt"], key: "com.android.art.key", required: [ @@ -269,7 +263,7 @@ apex_defaults { // Release version of the ART APEX module (not containing debug // variants nor tools), included in user builds. Also used for // storage-constrained devices in userdebug and eng builds. -apex { +art_apex { name: "com.android.art.release", defaults: ["com.android.art-defaults"], certificate: ":com.android.art.certificate", @@ -278,7 +272,7 @@ apex { // "Debug" version of the ART APEX module (containing both release and // debug variants, as well as additional tools), included in userdebug and // eng build. -apex { +art_apex { name: "com.android.art.debug", defaults: ["com.android.art-dev-defaults"], certificate: ":com.android.art.certificate", @@ -311,7 +305,7 @@ art_gtests = [ // "Testing" version of the ART APEX module (containing both release // and debug variants, additional tools, and ART gtests), for testing // purposes only. -apex_test { +art_apex_test { name: "com.android.art.testing", defaults: ["com.android.art-dev-defaults"], file_contexts: "com.android.art.debug", @@ -462,36 +456,3 @@ cc_prebuilt_binary { defaults: ["art-check-apex-gen-fakebin-defaults"], srcs: [":art-check-testing-apex-gen"], } - -// Pre-install scripts. - -sh_binary { - name: "art_preinstall_hook", - src: "art_preinstall_hook.sh", -} - -sh_binary { - name: "art_preinstall_hook_boot", - src: "art_preinstall_hook_boot.sh", -} - -sh_binary { - name: "art_preinstall_hook_system_server", - src: "art_preinstall_hook_system_server.sh", -} - -sh_binary { - name: "art_prepostinstall_utils", - src: "art_prepostinstall_utils.sh", -} - -sh_binary { - name: "art_postinstall_hook", - src: "art_postinstall_hook.sh", -} - -sh_binary { - name: "art_apex_boot_integrity", - src: "art_apex_boot_integrity.sh", - init_rc: ["art_apex_boot_integrity.rc"], -} diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py index 248fab5275..7d2c79af23 100755 --- a/build/apex/art_apex_test.py +++ b/build/apex/art_apex_test.py @@ -267,6 +267,14 @@ class Checker: return False, '%s is a directory' return True, '' + def is_dir(self, path): + fs_object = self._provider.get(path) + if fs_object is None: + return False, 'Could not find %s' + if not fs_object.is_dir: + return False, '%s is not a directory' + return True, '' + def check_file(self, path): ok, msg = self.is_file(path) if not ok: @@ -294,27 +302,30 @@ class Checker: self.fail('%s is not a symlink', path) self._expected_file_globs.add(path) - def check_art_test_executable(self, filename): - # This is a simplistic implementation, as we declare victory as soon as the - # test binary is found for one of the supported (not built) architectures. - # Ideally we would propagate the built architectures from the build system - # to this script and require test binaries for all of them to be present. - # Note that this behavior is not specific to this method: there are other - # places in this script where we rely on this simplified strategy. + def arch_dirs_for_path(self, path): + # Look for target-specific subdirectories for the given directory path. + # This is needed because the list of build targets is not propagated + # to this script. # - # TODO: Implement the suggestion above (here and in other places in this - # script). - test_found = False + # TODO: Pass build target information to this script and fix all places + # where this function in used (or similar workarounds). + dirs = [] for arch in ARCHS: - test_path = '%s/%s/%s' % (ART_TEST_DIR, arch, filename) - test_is_file, _ = self.is_file(test_path) - if test_is_file: - test_found = True - self._expected_file_globs.add(test_path) - if not self._provider.get(test_path).is_exec: - self.fail('%s is not executable', test_path) - if not test_found: + dir = '%s/%s' % (path, arch) + found, _ = self.is_dir(dir) + if found: + dirs.append(dir) + return dirs + + def check_art_test_executable(self, filename): + dirs = self.arch_dirs_for_path(ART_TEST_DIR) + if not dirs: self.fail('ART test binary missing: %s', filename) + for dir in dirs: + test_path = '%s/%s' % (dir, filename) + self._expected_file_globs.add(test_path) + if not self._provider.get(test_path).is_exec: + self.fail('%s is not executable', test_path) def check_single_library(self, filename): lib_path = 'lib/%s' % filename @@ -328,6 +339,14 @@ class Checker: if not lib_is_file and not lib64_is_file: self.fail('Library missing: %s', filename) + def check_dexpreopt(self, basename): + dirs = self.arch_dirs_for_path('javalib') + if not dirs: + self.fail('Could not find javalib directory for any arch.') + for dir in dirs: + for ext in ['art', 'oat', 'vdex']: + self.check_file('%s/%s.%s' % (dir, basename, ext)) + def check_java_library(self, basename): return self.check_file('javalib/%s.jar' % basename) @@ -467,6 +486,7 @@ class ReleaseChecker: self._checker.check_native_library('libnativebridge') self._checker.check_native_library('libnativehelper') self._checker.check_native_library('libnativeloader') + self._checker.check_native_library('libadbconnection_server') # Check internal libraries for ART. self._checker.check_native_library('libadbconnection') @@ -522,6 +542,17 @@ class ReleaseChecker: self._checker.check_optional_native_library('libclang_rt.hwasan*') self._checker.check_optional_native_library('libclang_rt.ubsan*') + # Check dexpreopt files for libcore bootclasspath jars, unless this is a + # coverage build with EMMA_INSTRUMENT_FRAMEWORK=true (in that case we do not + # generate dexpreopt files because ART boot jars depend on framework and + # cannot be dexpreopted in isolation). + if 'EMMA_INSTRUMENT_FRAMEWORK' not in os.environ or not os.environ['EMMA_INSTRUMENT_FRAMEWORK']: + self._checker.check_dexpreopt('boot') + self._checker.check_dexpreopt('boot-apache-xml') + self._checker.check_dexpreopt('boot-bouncycastle') + self._checker.check_dexpreopt('boot-core-icu4j') + self._checker.check_dexpreopt('boot-core-libart') + self._checker.check_dexpreopt('boot-okhttp') class ReleaseTargetChecker: def __init__(self, checker): @@ -531,13 +562,6 @@ class ReleaseTargetChecker: return 'Release (Target) Checker' def run(self): - # Check the APEX package scripts. - self._checker.check_executable('art_postinstall_hook') - self._checker.check_executable('art_preinstall_hook') - self._checker.check_executable('art_preinstall_hook_boot') - self._checker.check_executable('art_preinstall_hook_system_server') - self._checker.check_executable('art_prepostinstall_utils') - # Check binaries for ART. self._checker.check_executable('oatdump') @@ -1045,36 +1069,12 @@ def art_apex_test_main(test_args): if test_args.host and test_args.flattened: logging.error("Both of --host and --flattened set") return 1 - if test_args.tree and test_args.debug: - logging.error("Both of --tree and --debug set") - return 1 - if test_args.tree and test_args.testing: - logging.error("Both of --tree and --testing set") - return 1 - if test_args.list and test_args.debug: - logging.error("Both of --list and --debug set") - return 1 - if test_args.list and test_args.testing: - logging.error("Both of --list and --testing set") - return 1 if test_args.list and test_args.tree: logging.error("Both of --list and --tree set") return 1 if test_args.size and not (test_args.list or test_args.tree): logging.error("--size set but neither --list nor --tree set") return 1 - if test_args.host and test_args.testing: - logging.error("Both of --host and --testing set") - return 1 - if test_args.debug and test_args.testing: - logging.error("Both of --debug and --testing set") - return 1 - if test_args.flavor and test_args.debug: - logging.error("Both of --flavor and --debug set") - return 1 - if test_args.flavor and test_args.testing: - logging.error("Both of --flavor and --testing set") - return 1 if not test_args.flattened and not test_args.tmpdir: logging.error("Need a tmpdir.") return 1 @@ -1082,15 +1082,6 @@ def art_apex_test_main(test_args): logging.error("Need debugfs.") return 1 - # Handle legacy flavor flags. - if test_args.debug: - logging.warning('Using deprecated option --debug') - test_args.flavor = FLAVOR_DEBUG - if test_args.testing: - logging.warning('Using deprecated option --testing') - test_args.flavor = FLAVOR_TESTING - - # Handle new flavor flag. if test_args.flavor == FLAVOR_AUTO: logging.warning('--flavor=auto, trying to autodetect. This may be incorrect!') for flavor in [ FLAVOR_RELEASE, FLAVOR_DEBUG, FLAVOR_TESTING ]: @@ -1248,10 +1239,6 @@ if __name__ == "__main__": parser.add_argument('--flavor', help='Check as FLAVOR APEX', choices=FLAVORS_ALL, default=FLAVOR_AUTO) - # Deprecated flavor flags. - # TODO: Stop supporting those flags eventually. - parser.add_argument('--debug', help='Check as debug APEX', action='store_true') - parser.add_argument('--testing', help='Check as testing APEX', action='store_true') parser.add_argument('--list', help='List all files', action='store_true') parser.add_argument('--tree', help='Print directory tree', action='store_true') diff --git a/build/apex/art_postinstall_hook.sh b/build/apex/art_postinstall_hook.sh deleted file mode 100644 index cb3b887b76..0000000000 --- a/build/apex/art_postinstall_hook.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/system/bin/sh - -# 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. -# - -. `dirname $0`/art_prepostinstall_utils || exit 100 - -log_info "=== ART runtime post-install ===" - -# Check for OTA base folder. -if [ ! -d /data/ota/dalvik-cache ] ; then - log_error "Postinstall dalvik-cache does not exist or is not a directory" - exit 101 -fi - -log_info "Checking fsverity" - -# Measure (and enable) fsverity to see if things are installed. Enable is not -# idempotent, and we'd need to parse the error string to see whether it says -# data was installed. Rather do a two-step. -FILES=`find /data/ota/dalvik-cache -type f` -for FILE in $FILES ; do - fsverity measure $FILE && continue - ENABLE_MSG=`fsverity enable $FILE 2>&1` && continue - - # No installed data, can't enable. Clean up and fail. - log_error "Enable failed: $ENABLE_MSG" - rm -rf /data/ota/dalvik-cache - exit 200 -done - -log_info "Moving dalvik-cache" - -rm -rf /data/dalvik-cache/* || exit 102 -mv /data/ota/dalvik-cache/* /data/dalvik-cache/ || exit 103 -restorecon -R -F /data/dalvik-cache/* || exit 104 diff --git a/build/apex/art_preinstall_hook.sh b/build/apex/art_preinstall_hook.sh deleted file mode 100644 index 94a1b210ec..0000000000 --- a/build/apex/art_preinstall_hook.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/system/bin/sh - -# 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. -# - -. `dirname $0`/art_prepostinstall_utils || exit 100 - -log_info "=== ART runtime pre-install ===" - -set_arches || exit 101 -log_info "Arches = `echo $ARCHES`" - -# The runtime update uses /data/ota as a staging directory, similar to -# A/B OTA. (There is no overlap, as A/B uses slot prefixes.) - -# Create OTA base folder. -mkdir -p /data/ota/dalvik-cache || exit 102 -# Bind-mount to perceive as normal structure. -mount -o bind /data/ota/dalvik-cache /data/dalvik-cache || exit 103 - -for ARCH in $ARCHES ; do - log_info "Preparing compilation output directories for $ARCH" - - # Create OTA folders. - mkdir -p /data/ota/dalvik-cache/$ARCH || exit 104 - rm -rf /data/ota/dalvik-cache/$ARCH/* || exit 105 - - `dirname $0`/art_preinstall_hook_boot $ARCH || exit 200 -done - -PRIMARY_ARCH=`echo $ARCHES | sed -e 's/ .*//'` -`dirname $0`/art_preinstall_hook_system_server $PRIMARY_ARCH || exit 300 - -FILES=`find /data/dalvik-cache -type f` -for FILE in $FILES ; do - setup_fsverity $FILE || exit 400 -done diff --git a/build/apex/art_preinstall_hook_boot.sh b/build/apex/art_preinstall_hook_boot.sh deleted file mode 100644 index 0985befb9d..0000000000 --- a/build/apex/art_preinstall_hook_boot.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/system/bin/sh - -# 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. -# - -. `dirname $0`/art_prepostinstall_utils || exit 100 - -log_info "Preparing boot image compilation parameters" - -# Prefer DEX2OATBOOTCLASSPATH, then BOOTCLASSPATH. -USED_CLASSPATH=$DEX2OATBOOTCLASSPATH -if [ -z "$USED_CLASSPATH" ] ; then - USED_CLASSPATH=$BOOTCLASSPATH - if [ -z "$USED_CLASSPATH" ] ; then - log_error "Could not find boot class-path to compile" - exit 101 - fi -fi -BOOTCP=`echo $USED_CLASSPATH | tr ":" "\n"` - -DEX_FILES= -DEX_LOCATIONS= -for component in $BOOTCP ; do - DEX_FILES="$DEX_FILES --dex-file=$component" - DEX_LOCATIONS="$DEX_LOCATIONS --dex-location=$component" -done - -PROFILING= -if [ -f "/system/etc/boot-image.prof" ] ; then - PROFILING="--compiler-filter=speed-profile --profile-file=/system/etc/boot-image.prof" -fi -if [ -f "/system/etc/dirty-image-objects" ] ; then - PROFILING="$PROFILING --dirty-image-objects=/system/etc/dirty-image-objects" -fi - -DEX2OAT_IMAGE_XMX=`getprop dalvik.vm.image-dex2oat-Xmx` - -DEX2OAT_TARGET_ARCH=$1 -DEX2OAT_TARGET_CPU_VARIANT=`getprop dalvik.vm.isa.${DEX2OAT_TARGET_ARCH}.variant` -DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES=`getprop dalvik.vm.isa.${DEX2OAT_TARGET_ARCH}.features` - -log_info "Compiling boot image for $DEX2OAT_TARGET_ARCH" - -dex2oat \ - --avoid-storing-invocation \ - --runtime-arg -Xmx$DEX2OAT_IMAGE_XMX \ - $PROFILING \ - $DEX_FILES \ - $DEX_LOCATIONS \ - --generate-mini-debug-info \ - --strip \ - --oat-file=/data/dalvik-cache/$DEX2OAT_TARGET_ARCH/system@framework@boot.oat \ - --oat-location=/data/dalvik-cache/$DEX2OAT_TARGET_ARCH/system@framework@boot.oat \ - --image=/data/dalvik-cache/$DEX2OAT_TARGET_ARCH/system@framework@boot.art --base=0x70000000 \ - --instruction-set=$DEX2OAT_TARGET_ARCH \ - --instruction-set-variant=$DEX2OAT_TARGET_CPU_VARIANT \ - --instruction-set-features=$DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES \ - --android-root=/system \ - --no-inline-from=core-oj.jar \ - --abort-on-hard-verifier-error \ - --force-determinism || { log_error "Dex2oat failed" ; exit 102 ; } diff --git a/build/apex/art_preinstall_hook_system_server.sh b/build/apex/art_preinstall_hook_system_server.sh deleted file mode 100644 index 9462c3b885..0000000000 --- a/build/apex/art_preinstall_hook_system_server.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/system/bin/sh - -# 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. -# - -. `dirname $0`/art_prepostinstall_utils || exit 100 - -function dalvik_cache_name { - local input=$1 - # Strip first /, replace rest with @. - DALVIK_CACHE_NAME=`echo $input | sed -e 's,^/,,' -e 's,/,@,g'` - # Append @classes.dex. - DALVIK_CACHE_NAME="${DALVIK_CACHE_NAME}@classes.dex" -} - -log_info "Preparing system server compilation parameters" - -if [ "x$SYSTEMSERVERCLASSPATH" = "x" ] ; then - log_info "SYSTEMSERVERCLASSPATH is not set! Trying to retrieve from init.environ.rc." - SYSTEMSERVERCLASSPATH=`grep "export SYSTEMSERVERCLASSPATH" init.environ.rc | sed -e "s/.* //"` - if [ "x$SYSTEMSERVERCLASSPATH" = "x" ] ; then - log_error "Could not find SYSTEMSERVERCLASSPATH" - exit 101 - fi -fi -SYSCP=`echo $SYSTEMSERVERCLASSPATH | tr ":" "\n"` - -BOOTCPPARAM= -if [ ! -z "$DEX2OATBOOTCLASSPATH" ] ; then - BOOTCPPARAM="--runtime-arg -Xbootclasspath:$DEX2OATBOOTCLASSPATH" -fi - -DEX2OAT_IMAGE_XMX=`getprop dalvik.vm.dex2oat-Xmx` - -DEX2OAT_TARGET_ARCH=$1 -DEX2OAT_TARGET_CPU_VARIANT=`getprop dalvik.vm.isa.${DEX2OAT_TARGET_ARCH}.variant` -DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES=`getprop dalvik.vm.isa.${DEX2OAT_TARGET_ARCH}.features` - -# Do this like preopt: speed compile, no classpath, possibly pick up profiles. - -# TODO: App image? Would have to scan /system for an existing image. - -for COMPONENT in $SYSCP ; do - log_info "Compiling $COMPONENT" - dalvik_cache_name $COMPONENT - PROFILING= - if [ -f "${COMPONENT}.prof" ] ; then - PROFILING="--profile-file=${COMPONENT}.prof" - fi - dex2oat \ - --avoid-storing-invocation \ - --runtime-arg -Xmx$DEX2OAT_IMAGE_XMX \ - $BOOTCPPARAM \ - --class-loader-context=\& \ - --boot-image=/data/dalvik-cache/system@framework@boot.art \ - --dex-file=$COMPONENT \ - --dex-location=$COMPONENT \ - --oat-file=/data/dalvik-cache/$DEX2OAT_TARGET_ARCH/$DALVIK_CACHE_NAME \ - --android-root=/system \ - --instruction-set=$DEX2OAT_TARGET_ARCH \ - --instruction-set-variant=$DEX2OAT_TARGET_CPU_VARIANT \ - --instruction-set-features=$DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES \ - --no-generate-debug-info \ - --abort-on-hard-verifier-error \ - --force-determinism \ - --no-inline-from=core-oj.jar \ - --copy-dex-files=false \ - --compiler-filter=speed \ - --generate-mini-debug-info \ - $PROFILING \ - || { log_error "Dex2oat failed" ; exit 102 ; } -done diff --git a/build/apex/art_prepostinstall_utils.sh b/build/apex/art_prepostinstall_utils.sh deleted file mode 100644 index f5a94d12e3..0000000000 --- a/build/apex/art_prepostinstall_utils.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/system/bin/sh - -# 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. -# - -alias log_info="log -t art_apex -p i" -alias log_error="log -t art_apex -p f" - -# Set |ARCHES| to a string containing the architectures of the device. -function set_arches { - # Derive architectures. For now, stop at two. - local abilist_prop=`getprop ro.product.cpu.abilist` - local abilist=`echo $abilist_prop | tr "," "\n"` - ARCHES="" - for abi in $abilist ; do - case "$abi" in - arm64-v8a) - ARCHES="$ARCHES\narm64" - ;; - armeabi-v7a|armeabi) - ARCHES="$ARCHES\narm" - ;; - x86) - ARCHES="$ARCHES\nx86" - ;; - x86_64) - ARCHES="$ARCHES\nx86_64" - ;; - *) - log_error "Unsupported ABI $abi" - return 1 - ;; - esac - done - ARCHES=`echo $ARCHES | uniq` - return 0 -} - -function setup_fsverity { - local full_shell_path=`readlink -f $0` - local bin_dir=`dirname $full_shell_path` - local apex_dir=`dirname $bin_dir` - local sig_dir="${apex_dir}.signatures" - local file=$1 - local signature_file="$sig_dir/$file.sig" - # Setup. - log_info "fsverity setup for $file" - SETUP_MSG=`fsverity setup $file --signature=$signature_file --hash=sha256 2>&1` || \ - { log_error "Setup failed: $SETUP_MSG" ; return 300 ; } - # Enable. - log_info "fsverity enable for $file" - ENABLE_MSG=`fsverity enable $file 2>&1` || \ - { log_error "Enable failed: $ENABLE_MSG" ; return 301 ; } - # Test integrity. - INTEGRITY_MSG=`dd if=$file of=/dev/null bs=4k 2>&1` || \ - { log_error "Integrity failed: $INTEGRITY_MSG" ; return 302 ; } - return 0 -} diff --git a/build/apex/manifest-art.json b/build/apex/manifest-art.json index ebba1b27ef..59cbfac435 100644 --- a/build/apex/manifest-art.json +++ b/build/apex/manifest-art.json @@ -1,6 +1,4 @@ { "name": "com.android.art", - "version": 1, - "preInstallHook": "bin/art_preinstall_hook", - "postInstallHook": "bin/art_postinstall_hook" + "version": 1 } diff --git a/build/art.go b/build/art.go index 56eec54138..4c1099bc98 100644 --- a/build/art.go +++ b/build/art.go @@ -312,6 +312,10 @@ func init() { android.RegisterModuleType("art_global_defaults", artGlobalDefaultsFactory) android.RegisterModuleType("art_debug_defaults", artDebugDefaultsFactory) + // ART apex is special because it must include dexpreopt files for bootclasspath jars. + android.RegisterModuleType("art_apex", artApexBundleFactory) + android.RegisterModuleType("art_apex_test", artTestApexBundleFactory) + // TODO: This makes the module disable itself for host if HOST_PREFER_32_BIT is // set. We need this because the multilib types of binaries listed in the apex // rule must match the declared type. This is normally not difficult but HOST_PREFER_32_BIT @@ -321,8 +325,16 @@ func init() { android.RegisterModuleType("art_apex_test_host", artHostTestApexBundleFactory) } +func artApexBundleFactory() android.Module { + return apex.ApexBundleFactory(false /*testApex*/, true /*artApex*/) +} + +func artTestApexBundleFactory() android.Module { + return apex.ApexBundleFactory(true /*testApex*/, true /*artApex*/) +} + func artHostTestApexBundleFactory() android.Module { - module := apex.ApexBundleFactory( /*testApex*/ true) + module := apex.ApexBundleFactory(true /*testApex*/, true /*artApex*/) android.AddLoadHook(module, func(ctx android.LoadHookContext) { if envTrue(ctx, "HOST_PREFER_32_BIT") { type props struct { diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 3a2988fbf5..10397e8f74 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -1183,7 +1183,7 @@ CPURegList CodeGeneratorARM64::GetFramePreservedCoreRegisters() const { CPURegList CodeGeneratorARM64::GetFramePreservedFPRegisters() const { DCHECK(ArtVixlRegCodeCoherentForRegSet(0, 0, fpu_spill_mask_, GetNumberOfFloatingPointRegisters())); - return CPURegList(CPURegister::kFPRegister, kDRegSize, + return CPURegList(CPURegister::kVRegister, kDRegSize, fpu_spill_mask_); } @@ -1316,10 +1316,10 @@ void CodeGeneratorARM64::MoveConstant(CPURegister destination, HConstant* consta } else if (constant->IsNullConstant()) { __ Mov(Register(destination), 0); } else if (constant->IsFloatConstant()) { - __ Fmov(FPRegister(destination), constant->AsFloatConstant()->GetValue()); + __ Fmov(VRegister(destination), constant->AsFloatConstant()->GetValue()); } else { DCHECK(constant->IsDoubleConstant()); - __ Fmov(FPRegister(destination), constant->AsDoubleConstant()->GetValue()); + __ Fmov(VRegister(destination), constant->AsDoubleConstant()->GetValue()); } } @@ -1343,7 +1343,7 @@ static bool CoherentConstantAndType(Location constant, DataType::Type type) { static CPURegister AcquireFPOrCoreCPURegisterOfSize(vixl::aarch64::MacroAssembler* masm, vixl::aarch64::UseScratchRegisterScope* temps, int size_in_bits) { - return masm->GetScratchFPRegisterList()->IsEmpty() + return masm->GetScratchVRegisterList()->IsEmpty() ? CPURegister(temps->AcquireRegisterOfSize(size_in_bits)) : CPURegister(temps->AcquireVRegisterOfSize(size_in_bits)); } @@ -1411,7 +1411,7 @@ void CodeGeneratorARM64::MoveLocation(Location destination, if (GetGraph()->HasSIMD()) { __ Mov(QRegisterFrom(destination), QRegisterFrom(source)); } else { - __ Fmov(FPRegister(dst), FPRegisterFrom(source, dst_type)); + __ Fmov(VRegister(dst), FPRegisterFrom(source, dst_type)); } } } @@ -1421,14 +1421,14 @@ void CodeGeneratorARM64::MoveLocation(Location destination, } else { DCHECK(source.IsSIMDStackSlot()); UseScratchRegisterScope temps(GetVIXLAssembler()); - if (GetVIXLAssembler()->GetScratchFPRegisterList()->IsEmpty()) { + if (GetVIXLAssembler()->GetScratchVRegisterList()->IsEmpty()) { Register temp = temps.AcquireX(); __ Ldr(temp, MemOperand(sp, source.GetStackIndex())); __ Str(temp, MemOperand(sp, destination.GetStackIndex())); __ Ldr(temp, MemOperand(sp, source.GetStackIndex() + kArm64WordSize)); __ Str(temp, MemOperand(sp, destination.GetStackIndex() + kArm64WordSize)); } else { - FPRegister temp = temps.AcquireVRegisterOfSize(kQRegSize); + VRegister temp = temps.AcquireVRegisterOfSize(kQRegSize); __ Ldr(temp, StackOperandFrom(source)); __ Str(temp, StackOperandFrom(destination)); } @@ -1602,7 +1602,7 @@ void CodeGeneratorARM64::LoadAcquire(HInstruction* instruction, MaybeRecordImplicitNullCheck(instruction); } } - __ Fmov(FPRegister(dst), temp); + __ Fmov(VRegister(dst), temp); break; } case DataType::Type::kUint32: @@ -1702,7 +1702,7 @@ void CodeGeneratorARM64::StoreRelease(HInstruction* instruction, } else { DCHECK(src.IsFPRegister()); temp_src = src.Is64Bits() ? temps.AcquireX() : temps.AcquireW(); - __ Fmov(temp_src, FPRegister(src)); + __ Fmov(temp_src, VRegister(src)); } { ExactAssemblyScope eas(masm, kInstructionSize, CodeBufferCheckScope::kExactSize); @@ -2057,9 +2057,9 @@ void InstructionCodeGeneratorARM64::HandleBinaryOp(HBinaryOperation* instr) { } case DataType::Type::kFloat32: case DataType::Type::kFloat64: { - FPRegister dst = OutputFPRegister(instr); - FPRegister lhs = InputFPRegisterAt(instr, 0); - FPRegister rhs = InputFPRegisterAt(instr, 1); + VRegister dst = OutputFPRegister(instr); + VRegister lhs = InputFPRegisterAt(instr, 0); + VRegister rhs = InputFPRegisterAt(instr, 1); if (instr->IsAdd()) { __ Fadd(dst, lhs, rhs); } else if (instr->IsSub()) { @@ -2805,7 +2805,7 @@ static bool IsFloatingPointZeroConstant(HInstruction* inst) { } void InstructionCodeGeneratorARM64::GenerateFcmp(HInstruction* instruction) { - FPRegister lhs_reg = InputFPRegisterAt(instruction, 0); + VRegister lhs_reg = InputFPRegisterAt(instruction, 0); Location rhs_loc = instruction->GetLocations()->InAt(1); if (rhs_loc.IsConstant()) { // 0.0 is the only immediate that can be encoded directly in @@ -5428,8 +5428,8 @@ void InstructionCodeGeneratorARM64::VisitAbs(HAbs* abs) { } case DataType::Type::kFloat32: case DataType::Type::kFloat64: { - FPRegister in_reg = InputFPRegisterAt(abs, 0); - FPRegister out_reg = OutputFPRegister(abs); + VRegister in_reg = InputFPRegisterAt(abs, 0); + VRegister out_reg = OutputFPRegister(abs); __ Fabs(out_reg, in_reg); break; } diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 1a9b7006dc..a669094509 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -63,7 +63,7 @@ static const vixl::aarch64::Register kParameterCoreRegisters[] = { vixl::aarch64::x7 }; static constexpr size_t kParameterCoreRegistersLength = arraysize(kParameterCoreRegisters); -static const vixl::aarch64::FPRegister kParameterFPRegisters[] = { +static const vixl::aarch64::VRegister kParameterFPRegisters[] = { vixl::aarch64::d0, vixl::aarch64::d1, vixl::aarch64::d2, @@ -111,7 +111,7 @@ const vixl::aarch64::CPURegList callee_saved_core_registers( ? vixl::aarch64::x21.GetCode() : vixl::aarch64::x20.GetCode()), vixl::aarch64::x30.GetCode()); -const vixl::aarch64::CPURegList callee_saved_fp_registers(vixl::aarch64::CPURegister::kFPRegister, +const vixl::aarch64::CPURegList callee_saved_fp_registers(vixl::aarch64::CPURegister::kVRegister, vixl::aarch64::kDRegSize, vixl::aarch64::d8.GetCode(), vixl::aarch64::d15.GetCode()); @@ -162,7 +162,7 @@ static const vixl::aarch64::Register kRuntimeParameterCoreRegisters[] = vixl::aarch64::x7 }; static constexpr size_t kRuntimeParameterCoreRegistersLength = arraysize(kRuntimeParameterCoreRegisters); -static const vixl::aarch64::FPRegister kRuntimeParameterFpuRegisters[] = +static const vixl::aarch64::VRegister kRuntimeParameterFpuRegisters[] = { vixl::aarch64::d0, vixl::aarch64::d1, vixl::aarch64::d2, @@ -175,7 +175,7 @@ static constexpr size_t kRuntimeParameterFpuRegistersLength = arraysize(kRuntimeParameterCoreRegisters); class InvokeRuntimeCallingConvention : public CallingConvention<vixl::aarch64::Register, - vixl::aarch64::FPRegister> { + vixl::aarch64::VRegister> { public: static constexpr size_t kParameterCoreRegistersLength = arraysize(kParameterCoreRegisters); @@ -193,7 +193,7 @@ class InvokeRuntimeCallingConvention : public CallingConvention<vixl::aarch64::R }; class InvokeDexCallingConvention : public CallingConvention<vixl::aarch64::Register, - vixl::aarch64::FPRegister> { + vixl::aarch64::VRegister> { public: InvokeDexCallingConvention() : CallingConvention(kParameterCoreRegisters, @@ -480,7 +480,7 @@ class CodeGeneratorARM64 : public CodeGenerator { // requirements, etc.). This also facilitates our task as all other registers // can easily be mapped via to or from their type and index or code. static const int kNumberOfAllocatableRegisters = vixl::aarch64::kNumberOfRegisters - 1; - static const int kNumberOfAllocatableFPRegisters = vixl::aarch64::kNumberOfFPRegisters; + static const int kNumberOfAllocatableFPRegisters = vixl::aarch64::kNumberOfVRegisters; static constexpr int kNumberOfAllocatableRegisterPairs = 0; void DumpCoreRegister(std::ostream& stream, int reg) const override; diff --git a/compiler/optimizing/code_generator_vector_x86.cc b/compiler/optimizing/code_generator_vector_x86.cc index 68aef779f2..1390af2435 100644 --- a/compiler/optimizing/code_generator_vector_x86.cc +++ b/compiler/optimizing/code_generator_vector_x86.cc @@ -1201,11 +1201,38 @@ void InstructionCodeGeneratorX86::VisitVecSADAccumulate(HVecSADAccumulate* instr } void LocationsBuilderX86::VisitVecDotProd(HVecDotProd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(instruction); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetInAt(2, Location::RequiresFpuRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresFpuRegister()); } void InstructionCodeGeneratorX86::VisitVecDotProd(HVecDotProd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + bool cpu_has_avx = CpuHasAvxFeatureFlag(); + LocationSummary* locations = instruction->GetLocations(); + XmmRegister acc = locations->InAt(0).AsFpuRegister<XmmRegister>(); + XmmRegister left = locations->InAt(1).AsFpuRegister<XmmRegister>(); + XmmRegister right = locations->InAt(2).AsFpuRegister<XmmRegister>(); + switch (instruction->GetPackedType()) { + case DataType::Type::kInt32: { + DCHECK_EQ(4u, instruction->GetVectorLength()); + XmmRegister tmp = locations->GetTemp(0).AsFpuRegister<XmmRegister>(); + if (!cpu_has_avx) { + __ movaps(tmp, right); + __ pmaddwd(tmp, left); + __ paddd(acc, tmp); + } else { + __ vpmaddwd(tmp, left, right); + __ vpaddd(acc, acc, tmp); + } + break; + } + default: + LOG(FATAL) << "Unsupported SIMD Type" << instruction->GetPackedType(); + UNREACHABLE(); + } } // Helper to set up locations for vector memory operations. diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc index 19dfd1d2a8..7fac44dea8 100644 --- a/compiler/optimizing/code_generator_vector_x86_64.cc +++ b/compiler/optimizing/code_generator_vector_x86_64.cc @@ -1174,11 +1174,38 @@ void InstructionCodeGeneratorX86_64::VisitVecSADAccumulate(HVecSADAccumulate* in } void LocationsBuilderX86_64::VisitVecDotProd(HVecDotProd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + LocationSummary* locations = new (GetGraph()->GetAllocator()) LocationSummary(instruction); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetInAt(1, Location::RequiresFpuRegister()); + locations->SetInAt(2, Location::RequiresFpuRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresFpuRegister()); } void InstructionCodeGeneratorX86_64::VisitVecDotProd(HVecDotProd* instruction) { - LOG(FATAL) << "No SIMD for " << instruction->GetId(); + bool cpu_has_avx = CpuHasAvxFeatureFlag(); + LocationSummary* locations = instruction->GetLocations(); + XmmRegister acc = locations->InAt(0).AsFpuRegister<XmmRegister>(); + XmmRegister left = locations->InAt(1).AsFpuRegister<XmmRegister>(); + XmmRegister right = locations->InAt(2).AsFpuRegister<XmmRegister>(); + switch (instruction->GetPackedType()) { + case DataType::Type::kInt32: { + DCHECK_EQ(4u, instruction->GetVectorLength()); + XmmRegister tmp = locations->GetTemp(0).AsFpuRegister<XmmRegister>(); + if (!cpu_has_avx) { + __ movaps(tmp, right); + __ pmaddwd(tmp, left); + __ paddd(acc, tmp); + } else { + __ vpmaddwd(tmp, left, right); + __ vpaddd(acc, acc, tmp); + } + break; + } + default: + LOG(FATAL) << "Unsupported SIMD Type" << instruction->GetPackedType(); + UNREACHABLE(); + } } // Helper to set up locations for vector memory operations. diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h index 5556f16740..9c80f323ba 100644 --- a/compiler/optimizing/common_arm64.h +++ b/compiler/optimizing/common_arm64.h @@ -87,36 +87,36 @@ inline vixl::aarch64::Register InputRegisterAt(HInstruction* instr, int input_in instr->InputAt(input_index)->GetType()); } -inline vixl::aarch64::FPRegister DRegisterFrom(Location location) { +inline vixl::aarch64::VRegister DRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; - return vixl::aarch64::FPRegister::GetDRegFromCode(location.reg()); + return vixl::aarch64::VRegister::GetDRegFromCode(location.reg()); } -inline vixl::aarch64::FPRegister QRegisterFrom(Location location) { +inline vixl::aarch64::VRegister QRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; - return vixl::aarch64::FPRegister::GetQRegFromCode(location.reg()); + return vixl::aarch64::VRegister::GetQRegFromCode(location.reg()); } -inline vixl::aarch64::FPRegister VRegisterFrom(Location location) { +inline vixl::aarch64::VRegister VRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; - return vixl::aarch64::FPRegister::GetVRegFromCode(location.reg()); + return vixl::aarch64::VRegister::GetVRegFromCode(location.reg()); } -inline vixl::aarch64::FPRegister SRegisterFrom(Location location) { +inline vixl::aarch64::VRegister SRegisterFrom(Location location) { DCHECK(location.IsFpuRegister()) << location; - return vixl::aarch64::FPRegister::GetSRegFromCode(location.reg()); + return vixl::aarch64::VRegister::GetSRegFromCode(location.reg()); } -inline vixl::aarch64::FPRegister FPRegisterFrom(Location location, DataType::Type type) { +inline vixl::aarch64::VRegister FPRegisterFrom(Location location, DataType::Type type) { DCHECK(DataType::IsFloatingPointType(type)) << type; return type == DataType::Type::kFloat64 ? DRegisterFrom(location) : SRegisterFrom(location); } -inline vixl::aarch64::FPRegister OutputFPRegister(HInstruction* instr) { +inline vixl::aarch64::VRegister OutputFPRegister(HInstruction* instr) { return FPRegisterFrom(instr->GetLocations()->Out(), instr->GetType()); } -inline vixl::aarch64::FPRegister InputFPRegisterAt(HInstruction* instr, int input_index) { +inline vixl::aarch64::VRegister InputFPRegisterAt(HInstruction* instr, int input_index) { return FPRegisterFrom(instr->GetLocations()->InAt(input_index), instr->InputAt(input_index)->GetType()); } @@ -201,7 +201,7 @@ inline Location LocationFrom(const vixl::aarch64::Register& reg) { return Location::RegisterLocation(ARTRegCodeFromVIXL(reg.GetCode())); } -inline Location LocationFrom(const vixl::aarch64::FPRegister& fpreg) { +inline Location LocationFrom(const vixl::aarch64::VRegister& fpreg) { return Location::FpuRegisterLocation(fpreg.GetCode()); } diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index 185d487dff..c48aaf5904 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -90,8 +90,8 @@ static void MoveFromReturnRegister(Location trg, Register res_reg = RegisterFrom(ARM64ReturnLocation(type), type); __ Mov(trg_reg, res_reg, kDiscardForSameWReg); } else { - FPRegister trg_reg = FPRegisterFrom(trg, type); - FPRegister res_reg = FPRegisterFrom(ARM64ReturnLocation(type), type); + VRegister trg_reg = FPRegisterFrom(trg, type); + VRegister res_reg = FPRegisterFrom(ARM64ReturnLocation(type), type); __ Fmov(trg_reg, res_reg); } } @@ -435,7 +435,7 @@ static void GenBitCount(HInvoke* instr, DataType::Type type, MacroAssembler* mas Register src = InputRegisterAt(instr, 0); Register dst = RegisterFrom(instr->GetLocations()->Out(), type); - FPRegister fpr = (type == DataType::Type::kInt64) ? temps.AcquireD() : temps.AcquireS(); + VRegister fpr = (type == DataType::Type::kInt64) ? temps.AcquireD() : temps.AcquireS(); __ Fmov(fpr, src); __ Cnt(fpr.V8B(), fpr.V8B()); @@ -591,8 +591,8 @@ static void GenMathRound(HInvoke* invoke, bool is_double, vixl::aarch64::MacroAs // For example, FCVTPS(-1.9) = -1 and FCVTPS(1.1) = 2. // If we were using this instruction, for most inputs, more handling code would be needed. LocationSummary* l = invoke->GetLocations(); - FPRegister in_reg = is_double ? DRegisterFrom(l->InAt(0)) : SRegisterFrom(l->InAt(0)); - FPRegister tmp_fp = is_double ? DRegisterFrom(l->GetTemp(0)) : SRegisterFrom(l->GetTemp(0)); + VRegister in_reg = is_double ? DRegisterFrom(l->InAt(0)) : SRegisterFrom(l->InAt(0)); + VRegister tmp_fp = is_double ? DRegisterFrom(l->GetTemp(0)) : SRegisterFrom(l->GetTemp(0)); Register out_reg = is_double ? XRegisterFrom(l->Out()) : WRegisterFrom(l->Out()); vixl::aarch64::Label done; @@ -2015,7 +2015,7 @@ void IntrinsicCodeGeneratorARM64::VisitStringGetCharsNoCheck(HInvoke* invoke) { if (mirror::kUseStringCompression) { // For compressed strings, acquire a SIMD temporary register. - FPRegister vtmp1 = temps.AcquireVRegisterOfSize(kQRegSize); + VRegister vtmp1 = temps.AcquireVRegisterOfSize(kQRegSize); const size_t c_char_size = DataType::Size(DataType::Type::kInt8); DCHECK_EQ(c_char_size, 1u); __ Bind(&compressed_string_preloop); @@ -3210,12 +3210,36 @@ void IntrinsicCodeGeneratorARM64::VisitFP16ToFloat(HInvoke* invoke) { MacroAssembler* masm = GetVIXLAssembler(); UseScratchRegisterScope scratch_scope(masm); Register bits = InputRegisterAt(invoke, 0); - FPRegister out = SRegisterFrom(invoke->GetLocations()->Out()); - FPRegister half = scratch_scope.AcquireH(); + VRegister out = SRegisterFrom(invoke->GetLocations()->Out()); + VRegister half = scratch_scope.AcquireH(); __ Fmov(half, bits); // ARMv8.2 __ Fcvt(out, half); } +void IntrinsicLocationsBuilderARM64::VisitFP16ToHalf(HInvoke* invoke) { + if (!codegen_->GetInstructionSetFeatures().HasFP16()) { + return; + } + + LocationSummary* locations = new (allocator_) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::RequiresFpuRegister()); + locations->SetOut(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARM64::VisitFP16ToHalf(HInvoke* invoke) { + DCHECK(codegen_->GetInstructionSetFeatures().HasFP16()); + MacroAssembler* masm = GetVIXLAssembler(); + UseScratchRegisterScope scratch_scope(masm); + VRegister in = SRegisterFrom(invoke->GetLocations()->InAt(0)); + VRegister half = scratch_scope.AcquireH(); + Register out = WRegisterFrom(invoke->GetLocations()->Out()); + __ Fcvt(half, in); + __ Fmov(out, half); + __ Sxth(out, out); // sign extend due to returning a short type. +} + UNIMPLEMENTED_INTRINSIC(ARM64, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(ARM64, StringStringIndexOf); diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc index 65f388837d..74e861fa8e 100644 --- a/compiler/optimizing/intrinsics_arm_vixl.cc +++ b/compiler/optimizing/intrinsics_arm_vixl.cc @@ -3071,6 +3071,7 @@ UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32Update) UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(ARMVIXL, CRC32UpdateByteBuffer) UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16ToFloat) +UNIMPLEMENTED_INTRINSIC(ARMVIXL, FP16ToHalf) UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(ARMVIXL, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index f71d281d5a..b18bbdde2d 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -2708,6 +2708,7 @@ UNIMPLEMENTED_INTRINSIC(MIPS, CRC32Update) UNIMPLEMENTED_INTRINSIC(MIPS, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(MIPS, CRC32UpdateByteBuffer) UNIMPLEMENTED_INTRINSIC(MIPS, FP16ToFloat) +UNIMPLEMENTED_INTRINSIC(MIPS, FP16ToHalf) UNIMPLEMENTED_INTRINSIC(MIPS, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(MIPS, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 7b87b03b50..e4627db33f 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -2358,6 +2358,7 @@ UNIMPLEMENTED_INTRINSIC(MIPS64, CRC32Update) UNIMPLEMENTED_INTRINSIC(MIPS64, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(MIPS64, CRC32UpdateByteBuffer) UNIMPLEMENTED_INTRINSIC(MIPS64, FP16ToFloat) +UNIMPLEMENTED_INTRINSIC(MIPS64, FP16ToHalf) UNIMPLEMENTED_INTRINSIC(MIPS64, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(MIPS64, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index 5a622ca6d1..95aa4c0eaa 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -3082,6 +3082,7 @@ UNIMPLEMENTED_INTRINSIC(X86, CRC32Update) UNIMPLEMENTED_INTRINSIC(X86, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(X86, CRC32UpdateByteBuffer) UNIMPLEMENTED_INTRINSIC(X86, FP16ToFloat) +UNIMPLEMENTED_INTRINSIC(X86, FP16ToHalf) UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(X86, StringStringIndexOfAfter); diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc index cbf66069fe..8dbc0d3062 100644 --- a/compiler/optimizing/intrinsics_x86_64.cc +++ b/compiler/optimizing/intrinsics_x86_64.cc @@ -2749,6 +2749,7 @@ UNIMPLEMENTED_INTRINSIC(X86_64, CRC32Update) UNIMPLEMENTED_INTRINSIC(X86_64, CRC32UpdateBytes) UNIMPLEMENTED_INTRINSIC(X86_64, CRC32UpdateByteBuffer) UNIMPLEMENTED_INTRINSIC(X86_64, FP16ToFloat) +UNIMPLEMENTED_INTRINSIC(X86_64, FP16ToHalf) UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOf); UNIMPLEMENTED_INTRINSIC(X86_64, StringStringIndexOfAfter); diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc index 9c4e9d25f7..567a41e2fd 100644 --- a/compiler/optimizing/loop_optimization.cc +++ b/compiler/optimizing/loop_optimization.cc @@ -1623,14 +1623,20 @@ bool HLoopOptimization::TrySetVectorType(DataType::Type type, uint64_t* restrict kNoDotProd; return TrySetVectorLength(16); case DataType::Type::kUint16: - case DataType::Type::kInt16: *restrictions |= kNoDiv | kNoAbs | kNoSignedHAdd | kNoUnroundedHAdd | - kNoSAD| + kNoSAD | kNoDotProd; return TrySetVectorLength(8); + case DataType::Type::kInt16: + *restrictions |= kNoDiv | + kNoAbs | + kNoSignedHAdd | + kNoUnroundedHAdd | + kNoSAD; + return TrySetVectorLength(8); case DataType::Type::kInt32: *restrictions |= kNoDiv | kNoSAD; return TrySetVectorLength(4); @@ -2166,7 +2172,7 @@ bool HLoopOptimization::VectorizeDotProdIdiom(LoopNode* node, bool generate_code, DataType::Type reduction_type, uint64_t restrictions) { - if (!instruction->IsAdd() || (reduction_type != DataType::Type::kInt32)) { + if (!instruction->IsAdd() || reduction_type != DataType::Type::kInt32) { return false; } diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc index e5ff8a886a..f722cf91a7 100644 --- a/compiler/optimizing/scheduler.cc +++ b/compiler/optimizing/scheduler.cc @@ -282,6 +282,36 @@ static bool IsBetterCandidateWithMoreLikelyDependencies(HInstruction* new_candid } } +void SchedulingGraph::AddCrossIterationDependencies(SchedulingNode* node) { + for (HInstruction* instruction : node->GetInstruction()->GetInputs()) { + // Having a phi-function from a loop header as an input means the current node of the + // scheduling graph has a cross-iteration dependency because such phi-functions bring values + // from the previous iteration to the current iteration. + if (!instruction->IsLoopHeaderPhi()) { + continue; + } + for (HInstruction* phi_input : instruction->GetInputs()) { + // As a scheduling graph of the current basic block is built by + // processing instructions bottom-up, nullptr returned by GetNode means + // an instruction defining a value for the phi is either before the + // instruction represented by node or it is in a different basic block. + SchedulingNode* def_node = GetNode(phi_input); + + // We don't create a dependency if there are uses besides the use in phi. + // In such cases a register to hold phi_input is usually allocated and + // a MOV instruction is generated. In cases with multiple uses and no MOV + // instruction, reordering creating a MOV instruction can improve + // performance more than an attempt to avoid a MOV instruction. + if (def_node != nullptr && def_node != node && phi_input->GetUses().HasExactlyOneElement()) { + // We have an implicit data dependency between node and def_node. + // AddAddDataDependency cannot be used because it is for explicit data dependencies. + // So AddOtherDependency is used. + AddOtherDependency(def_node, node); + } + } + } +} + void SchedulingGraph::AddDependencies(SchedulingNode* instruction_node, bool is_scheduling_barrier) { HInstruction* instruction = instruction_node->GetInstruction(); @@ -340,7 +370,11 @@ void SchedulingGraph::AddDependencies(SchedulingNode* instruction_node, if (other_node->IsSchedulingBarrier()) { // We have reached a scheduling barrier so we can stop further // processing. - DCHECK(other_node->HasOtherDependency(instruction_node)); + // + // As a "other" dependency is not set up if a data dependency exists, we need to check that + // one of them must exist. + DCHECK(other_node->HasOtherDependency(instruction_node) + || other_node->HasDataDependency(instruction_node)); break; } if (side_effect_dependency_analysis_.HasSideEffectDependency(other, instruction)) { @@ -372,6 +406,8 @@ void SchedulingGraph::AddDependencies(SchedulingNode* instruction_node, AddOtherDependency(GetNode(use.GetUser()->GetHolder()), instruction_node); } } + + AddCrossIterationDependencies(instruction_node); } static const std::string InstructionTypeId(const HInstruction* instruction) { diff --git a/compiler/optimizing/scheduler.h b/compiler/optimizing/scheduler.h index a97eda7e85..f7180a02d7 100644 --- a/compiler/optimizing/scheduler.h +++ b/compiler/optimizing/scheduler.h @@ -183,7 +183,9 @@ class SchedulingNode : public DeletableArenaObject<kArenaAllocScheduler> { void AddOtherPredecessor(SchedulingNode* predecessor) { // Check whether the predecessor has been added earlier. - if (HasOtherDependency(predecessor)) { + // As an optimization of the scheduling graph, we don't need to create another dependency if + // there is a data dependency between scheduling nodes. + if (HasOtherDependency(predecessor) || HasDataDependency(predecessor)) { return; } other_predecessors_.push_back(predecessor); @@ -362,6 +364,25 @@ class SchedulingGraph : public ValueObject { AddDependency(node, dependency, /*is_data_dependency*/false); } + // Analyze whether the scheduling node has cross-iteration dependencies which mean it uses + // values defined on the previous iteration. + // + // Supported cases: + // + // L: + // v2 = loop_head_phi(v1) + // instr1(v2) + // v1 = instr2 + // goto L + // + // In such cases moving instr2 before instr1 creates intersecting live ranges + // of v1 and v2. As a result a separate register is needed to keep the value + // defined by instr2 which is only used on the next iteration. + // If instr2 is not moved, no additional register is needed. The register + // used by instr1 is reused. + // To prevent such a situation a "other" dependency between instr1 and instr2 must be set. + void AddCrossIterationDependencies(SchedulingNode* node); + // Add dependencies nodes for the given `SchedulingNode`: inputs, environments, and side-effects. void AddDependencies(SchedulingNode* node, bool is_scheduling_barrier = false); diff --git a/compiler/utils/arm64/assembler_arm64.h b/compiler/utils/arm64/assembler_arm64.h index 594c6b4b75..fe2f1766c2 100644 --- a/compiler/utils/arm64/assembler_arm64.h +++ b/compiler/utils/arm64/assembler_arm64.h @@ -150,12 +150,12 @@ class Arm64Assembler final : public Assembler { return vixl::aarch64::Register::GetWRegFromCode(code); } - static vixl::aarch64::FPRegister reg_d(int code) { - return vixl::aarch64::FPRegister::GetDRegFromCode(code); + static vixl::aarch64::VRegister reg_d(int code) { + return vixl::aarch64::VRegister::GetDRegFromCode(code); } - static vixl::aarch64::FPRegister reg_s(int code) { - return vixl::aarch64::FPRegister::GetSRegFromCode(code); + static vixl::aarch64::VRegister reg_s(int code) { + return vixl::aarch64::VRegister::GetSRegFromCode(code); } private: diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc index d6ce03387c..0eab49f27d 100644 --- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc +++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc @@ -689,7 +689,7 @@ void Arm64JNIMacroAssembler::BuildFrame(size_t frame_size, const ManagedRegisterEntrySpills& entry_spills) { // Setup VIXL CPURegList for callee-saves. CPURegList core_reg_list(CPURegister::kRegister, kXRegSize, 0); - CPURegList fp_reg_list(CPURegister::kFPRegister, kDRegSize, 0); + CPURegList fp_reg_list(CPURegister::kVRegister, kDRegSize, 0); for (auto r : callee_save_regs) { Arm64ManagedRegister reg = r.AsArm64(); if (reg.IsXRegister()) { @@ -745,7 +745,7 @@ void Arm64JNIMacroAssembler::RemoveFrame(size_t frame_size, bool may_suspend) { // Setup VIXL CPURegList for callee-saves. CPURegList core_reg_list(CPURegister::kRegister, kXRegSize, 0); - CPURegList fp_reg_list(CPURegister::kFPRegister, kDRegSize, 0); + CPURegList fp_reg_list(CPURegister::kVRegister, kDRegSize, 0); for (auto r : callee_save_regs) { Arm64ManagedRegister reg = r.AsArm64(); if (reg.IsXRegister()) { diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index 166aec81a7..55f7691514 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -2268,6 +2268,20 @@ void X86Assembler::pmaddwd(XmmRegister dst, XmmRegister src) { } +void X86Assembler::vpmaddwd(XmmRegister dst, XmmRegister src1, XmmRegister src2) { + DCHECK(CpuHasAVXorAVX2FeatureFlag()); + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + uint8_t ByteZero = 0x00, ByteOne = 0x00; + ByteZero = EmitVexPrefixByteZero(/* is_twobyte_form=*/ true); + X86ManagedRegister vvvv_reg = X86ManagedRegister::FromXmmRegister(src1); + ByteOne = EmitVexPrefixByteOne(/*R=*/ false, vvvv_reg, SET_VEX_L_128, SET_VEX_PP_66); + EmitUint8(ByteZero); + EmitUint8(ByteOne); + EmitUint8(0xF5); + EmitXmmRegisterOperand(dst, src2); +} + + void X86Assembler::phaddw(XmmRegister dst, XmmRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index 1b6941c2e6..27fde26c80 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -577,6 +577,7 @@ class X86Assembler final : public Assembler { void pavgw(XmmRegister dst, XmmRegister src); void psadbw(XmmRegister dst, XmmRegister src); void pmaddwd(XmmRegister dst, XmmRegister src); + void vpmaddwd(XmmRegister dst, XmmRegister src1, XmmRegister src2); void phaddw(XmmRegister dst, XmmRegister src); void phaddd(XmmRegister dst, XmmRegister src); void haddps(XmmRegister dst, XmmRegister src); diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index 12d9646ace..9253730797 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -965,6 +965,11 @@ TEST_F(AssemblerX86Test, PMAddWD) { DriverStr(RepeatFF(&x86::X86Assembler::pmaddwd, "pmaddwd %{reg2}, %{reg1}"), "pmaddwd"); } +TEST_F(AssemblerX86AVXTest, VPMAddWD) { + DriverStr( + RepeatFFF(&x86::X86Assembler::vpmaddwd, "vpmaddwd %{reg3}, %{reg2}, %{reg1}"), "vpmaddwd"); +} + TEST_F(AssemblerX86Test, PHAddW) { DriverStr(RepeatFF(&x86::X86Assembler::phaddw, "phaddw %{reg2}, %{reg1}"), "phaddw"); } diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc index 64246aae0e..2c5dd9e949 100644 --- a/compiler/utils/x86_64/assembler_x86_64.cc +++ b/compiler/utils/x86_64/assembler_x86_64.cc @@ -71,6 +71,7 @@ bool X86_64Assembler::CpuHasAVXorAVX2FeatureFlag() { return false; } + void X86_64Assembler::call(CpuRegister reg) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitOptionalRex32(reg); @@ -758,7 +759,6 @@ void X86_64Assembler::movd(CpuRegister dst, XmmRegister src, bool is64bit) { EmitOperand(src.LowBits(), Operand(dst)); } - void X86_64Assembler::addss(XmmRegister dst, XmmRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0xF3); @@ -768,7 +768,6 @@ void X86_64Assembler::addss(XmmRegister dst, XmmRegister src) { EmitXmmRegisterOperand(dst.LowBits(), src); } - void X86_64Assembler::addss(XmmRegister dst, const Address& src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0xF3); @@ -2633,7 +2632,6 @@ void X86_64Assembler::xorps(XmmRegister dst, XmmRegister src) { EmitXmmRegisterOperand(dst.LowBits(), src); } - void X86_64Assembler::pxor(XmmRegister dst, XmmRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -3145,6 +3143,35 @@ void X86_64Assembler::pmaddwd(XmmRegister dst, XmmRegister src) { EmitXmmRegisterOperand(dst.LowBits(), src); } +void X86_64Assembler::vpmaddwd(XmmRegister dst, XmmRegister src1, XmmRegister src2) { + DCHECK(CpuHasAVXorAVX2FeatureFlag()); + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + bool is_twobyte_form = false; + uint8_t ByteZero = 0x00, ByteOne = 0x00, ByteTwo = 0x00; + if (!src2.NeedsRex()) { + is_twobyte_form = true; + } + ByteZero = EmitVexPrefixByteZero(is_twobyte_form); + X86_64ManagedRegister vvvv_reg = + X86_64ManagedRegister::FromXmmRegister(src1.AsFloatRegister()); + if (is_twobyte_form) { + ByteOne = EmitVexPrefixByteOne(dst.NeedsRex(), vvvv_reg, SET_VEX_L_128, SET_VEX_PP_66); + } else { + ByteOne = EmitVexPrefixByteOne(dst.NeedsRex(), + /*X=*/ false, + src2.NeedsRex(), + SET_VEX_M_0F); + ByteTwo = EmitVexPrefixByteTwo(/*W=*/ false, vvvv_reg, SET_VEX_L_128, SET_VEX_PP_66); + } + EmitUint8(ByteZero); + EmitUint8(ByteOne); + if (!is_twobyte_form) { + EmitUint8(ByteTwo); + } + EmitUint8(0xF5); + EmitXmmRegisterOperand(dst.LowBits(), src2); +} + void X86_64Assembler::phaddw(XmmRegister dst, XmmRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h index 15f3ab9f25..70072d9224 100644 --- a/compiler/utils/x86_64/assembler_x86_64.h +++ b/compiler/utils/x86_64/assembler_x86_64.h @@ -615,6 +615,7 @@ class X86_64Assembler final : public Assembler { void pavgw(XmmRegister dst, XmmRegister src); void psadbw(XmmRegister dst, XmmRegister src); void pmaddwd(XmmRegister dst, XmmRegister src); + void vpmaddwd(XmmRegister dst, XmmRegister src1, XmmRegister src2); void phaddw(XmmRegister dst, XmmRegister src); void phaddd(XmmRegister dst, XmmRegister src); void haddps(XmmRegister dst, XmmRegister src); diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index e3b8390468..3921c4afb0 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -1740,6 +1740,11 @@ TEST_F(AssemblerX86_64Test, Pmaddwd) { DriverStr(RepeatFF(&x86_64::X86_64Assembler::pmaddwd, "pmaddwd %{reg2}, %{reg1}"), "pmadwd"); } +TEST_F(AssemblerX86_64AVXTest, VPmaddwd) { + DriverStr(RepeatFFF(&x86_64::X86_64Assembler::vpmaddwd, + "vpmaddwd %{reg3}, %{reg2}, %{reg1}"), "vpmaddwd"); +} + TEST_F(AssemblerX86_64Test, Phaddw) { DriverStr(RepeatFF(&x86_64::X86_64Assembler::phaddw, "phaddw %{reg2}, %{reg1}"), "phaddw"); } diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index cd8f98d763..095f027ca3 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -63,6 +63,7 @@ #include "debug/method_debug_info.h" #include "dex/descriptors_names.h" #include "dex/dex_file-inl.h" +#include "dex/dex_file_loader.h" #include "dex/quick_compiler_callbacks.h" #include "dex/verification_results.h" #include "dex2oat_options.h" @@ -86,6 +87,7 @@ #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" +#include "oat.h" #include "oat_file.h" #include "oat_file_assistant.h" #include "profile/profile_compilation_info.h" @@ -280,6 +282,14 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" Do not include the arch as part of the name, it is added automatically."); UsageError(" Example: --boot-image=/system/framework/boot.art"); UsageError(" (specifies /system/framework/<arch>/boot.art as the image file)"); + UsageError(" Example: --boot-image=boot.art:boot-framework.art"); + UsageError(" (specifies <bcp-path1>/<arch>/boot.art as the image file and"); + UsageError(" <bcp-path2>/<arch>/boot-framework.art as the image extension file"); + UsageError(" with paths taken from corresponding boot class path components)"); + UsageError(" Example: --boot-image=/apex/com.android.art/boot.art:/system/framework/*:*"); + UsageError(" (specifies /apex/com.android.art/<arch>/boot.art as the image"); + UsageError(" file and search for extensions in /framework/system and boot"); + UsageError(" class path components' paths)"); UsageError(" Default: $ANDROID_ROOT/system/framework/boot.art"); UsageError(""); UsageError(" --android-root=<path>: used to locate libraries for portable linking."); @@ -752,16 +762,31 @@ class Dex2Oat final { void ProcessOptions(ParserOptions* parser_options) { compiler_options_->compile_pic_ = true; // All AOT compilation is PIC. + + if (android_root_.empty()) { + const char* android_root_env_var = getenv("ANDROID_ROOT"); + if (android_root_env_var == nullptr) { + Usage("--android-root unspecified and ANDROID_ROOT not set"); + } + android_root_ += android_root_env_var; + } + + if (!parser_options->boot_image_filename.empty()) { + boot_image_filename_ = parser_options->boot_image_filename; + } + DCHECK(compiler_options_->image_type_ == CompilerOptions::ImageType::kNone); if (!image_filenames_.empty()) { - if (android::base::EndsWith(image_filenames_[0], "apex.art")) { + if (!boot_image_filename_.empty()) { + compiler_options_->image_type_ = CompilerOptions::ImageType::kBootImageExtension; + } else if (android::base::EndsWith(image_filenames_[0], "apex.art")) { compiler_options_->image_type_ = CompilerOptions::ImageType::kApexBootImage; } else { compiler_options_->image_type_ = CompilerOptions::ImageType::kBootImage; } } if (app_image_fd_ != -1 || !app_image_file_name_.empty()) { - if (compiler_options_->IsBootImage()) { + if (compiler_options_->IsBootImage() || compiler_options_->IsBootImageExtension()) { Usage("Can't have both --image and (--app-image-fd or --app-image-file)"); } compiler_options_->image_type_ = CompilerOptions::ImageType::kAppImage; @@ -818,19 +843,9 @@ class Dex2Oat final { Usage("--oat-file arguments do not match --image arguments"); } - if (android_root_.empty()) { - const char* android_root_env_var = getenv("ANDROID_ROOT"); - if (android_root_env_var == nullptr) { - Usage("--android-root unspecified and ANDROID_ROOT not set"); - } - android_root_ += android_root_env_var; - } - - if (!IsBootImage() && parser_options->boot_image_filename.empty()) { - parser_options->boot_image_filename = GetDefaultBootImageLocation(android_root_); - } - if (!parser_options->boot_image_filename.empty()) { - boot_image_filename_ = parser_options->boot_image_filename; + if (!IsBootImage() && boot_image_filename_.empty()) { + DCHECK(!IsBootImageExtension()); + boot_image_filename_ = GetDefaultBootImageLocation(android_root_); } if (dex_filenames_.empty() && zip_fd_ == -1) { @@ -937,8 +952,8 @@ class Dex2Oat final { // Fill some values into the key-value store for the oat header. key_value_store_.reset(new SafeMap<std::string, std::string>()); - // Automatically force determinism for the boot image in a host build. - if (!kIsTargetBuild && IsBootImage()) { + // Automatically force determinism for the boot image and boot image extensions in a host build. + if (!kIsTargetBuild && (IsBootImage() || IsBootImageExtension())) { force_determinism_ = true; } compiler_options_->force_determinism_ = force_determinism_; @@ -961,18 +976,21 @@ class Dex2Oat final { if (image_filenames_[0].rfind('/') == std::string::npos) { Usage("Unusable boot image filename %s", image_filenames_[0].c_str()); } - image_filenames_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, image_filenames_[0]); + image_filenames_ = ImageSpace::ExpandMultiImageLocations( + ArrayRef<const std::string>(dex_locations_), image_filenames_[0], IsBootImageExtension()); if (oat_filenames_[0].rfind('/') == std::string::npos) { Usage("Unusable boot image oat filename %s", oat_filenames_[0].c_str()); } - oat_filenames_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, oat_filenames_[0]); + oat_filenames_ = ImageSpace::ExpandMultiImageLocations( + ArrayRef<const std::string>(dex_locations_), oat_filenames_[0], IsBootImageExtension()); if (!oat_unstripped_.empty()) { if (oat_unstripped_[0].rfind('/') == std::string::npos) { Usage("Unusable boot image symbol filename %s", oat_unstripped_[0].c_str()); } - oat_unstripped_ = ImageSpace::ExpandMultiImageLocations(dex_locations_, oat_unstripped_[0]); + oat_unstripped_ = ImageSpace::ExpandMultiImageLocations( + ArrayRef<const std::string>(dex_locations_), oat_unstripped_[0], IsBootImageExtension()); } } @@ -1206,7 +1224,7 @@ class Dex2Oat final { PruneNonExistentDexFiles(); // Expand oat and image filenames for multi image. - if (IsBootImage() && image_filenames_.size() == 1) { + if ((IsBootImage() || IsBootImageExtension()) && image_filenames_.size() == 1) { ExpandOatAndImageFilenames(); } @@ -1431,12 +1449,13 @@ class Dex2Oat final { return dex2oat::ReturnCode::kOther; } - // Verification results are null since we don't know if we will need them yet as the compler + // Verification results are null since we don't know if we will need them yet as the compiler // filter may change. callbacks_.reset(new QuickCompilerCallbacks( - IsBootImage() ? - CompilerCallbacks::CallbackMode::kCompileBootImage : - CompilerCallbacks::CallbackMode::kCompileApp)); + // For class verification purposes, boot image extension is the same as boot image. + (IsBootImage() || IsBootImageExtension()) + ? CompilerCallbacks::CallbackMode::kCompileBootImage + : CompilerCallbacks::CallbackMode::kCompileApp)); RuntimeArgumentMap runtime_options; if (!PrepareRuntimeOptions(&runtime_options, callbacks_.get())) { @@ -1489,7 +1508,7 @@ class Dex2Oat final { // store which is used for determining whether the oat file is up to date, // together with the boot class path locations and checksums stored below. CompilerFilter::Filter original_compiler_filter = compiler_options_->GetCompilerFilter(); - if (!IsBootImage() && IsVeryLarge(dex_files)) { + if (!IsBootImage() && !IsBootImageExtension() && IsVeryLarge(dex_files)) { // Disable app image to make sure dex2oat unloading is enabled. compiler_options_->image_type_ = CompilerOptions::ImageType::kNone; @@ -1512,14 +1531,64 @@ class Dex2Oat final { callbacks_->SetVerificationResults(verification_results_.get()); } - if (IsBootImage()) { - // For boot image, pass opened dex files to the Runtime::Create(). + if (IsBootImage() || IsBootImageExtension()) { + // For boot image or boot image extension, pass opened dex files to the Runtime::Create(). // Note: Runtime acquires ownership of these dex files. runtime_options.Set(RuntimeArgumentMap::BootClassPathDexList, &opened_dex_files_); } if (!CreateRuntime(std::move(runtime_options))) { return dex2oat::ReturnCode::kCreateRuntime; } + ArrayRef<const DexFile* const> bcp_dex_files(runtime_->GetClassLinker()->GetBootClassPath()); + if (IsBootImage() || IsBootImageExtension()) { + // Check boot class path dex files and, if compiling an extension, the images it depends on. + if ((IsBootImage() && bcp_dex_files.size() != dex_files.size()) || + (IsBootImageExtension() && bcp_dex_files.size() <= dex_files.size())) { + LOG(ERROR) << "Unexpected number of boot class path dex files for boot image or extension, " + << bcp_dex_files.size() << (IsBootImage() ? " != " : " <= ") << dex_files.size(); + return dex2oat::ReturnCode::kOther; + } + if (!std::equal(dex_files.begin(), dex_files.end(), bcp_dex_files.end() - dex_files.size())) { + LOG(ERROR) << "Boot class path dex files do not end with the compiled dex files."; + return dex2oat::ReturnCode::kOther; + } + size_t bcp_df_pos = 0u; + size_t bcp_df_end = bcp_dex_files.size(); + for (const std::string& bcp_location : runtime_->GetBootClassPathLocations()) { + if (bcp_df_pos == bcp_df_end || bcp_dex_files[bcp_df_pos]->GetLocation() != bcp_location) { + LOG(ERROR) << "Missing dex file for boot class component " << bcp_location; + return dex2oat::ReturnCode::kOther; + } + CHECK(!DexFileLoader::IsMultiDexLocation(bcp_dex_files[bcp_df_pos]->GetLocation().c_str())); + ++bcp_df_pos; + while (bcp_df_pos != bcp_df_end && + DexFileLoader::IsMultiDexLocation(bcp_dex_files[bcp_df_pos]->GetLocation().c_str())) { + ++bcp_df_pos; + } + } + if (bcp_df_pos != bcp_df_end) { + LOG(ERROR) << "Unexpected dex file in boot class path " + << bcp_dex_files[bcp_df_pos]->GetLocation(); + return dex2oat::ReturnCode::kOther; + } + auto lacks_image = [](const DexFile* df) { + if (kIsDebugBuild && df->GetOatDexFile() != nullptr) { + const OatFile* oat_file = df->GetOatDexFile()->GetOatFile(); + CHECK(oat_file != nullptr); + const auto& image_spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces(); + CHECK(std::any_of(image_spaces.begin(), + image_spaces.end(), + [=](const ImageSpace* space) { + return oat_file == space->GetOatFile(); + })); + } + return df->GetOatDexFile() == nullptr; + }; + if (std::any_of(bcp_dex_files.begin(), bcp_dex_files.end() - dex_files.size(), lacks_image)) { + LOG(ERROR) << "Missing required boot image(s) for boot image extension."; + return dex2oat::ReturnCode::kOther; + } + } if (!compilation_reason_.empty()) { key_value_store_->Put(OatHeader::kCompilationReasonKey, compilation_reason_); @@ -1529,17 +1598,32 @@ class Dex2Oat final { // If we're compiling the boot image, store the boot classpath into the Key-Value store. // We use this when loading the boot image. key_value_store_->Put(OatHeader::kBootClassPathKey, android::base::Join(dex_locations_, ':')); - } - - if (!IsBootImage()) { + } else if (IsBootImageExtension()) { + // Validate the boot class path and record the dependency on the loaded boot images. + TimingLogger::ScopedTiming t3("Loading image checksum", timings_); + Runtime* runtime = Runtime::Current(); + std::string full_bcp = android::base::Join(runtime->GetBootClassPathLocations(), ':'); + std::string extension_part = ":" + android::base::Join(dex_locations_, ':'); + if (!android::base::EndsWith(full_bcp, extension_part)) { + LOG(ERROR) << "Full boot class path does not end with extension parts, full: " << full_bcp + << ", extension: " << extension_part.substr(1u); + return dex2oat::ReturnCode::kOther; + } + std::string bcp_dependency = full_bcp.substr(0u, full_bcp.size() - extension_part.size()); + key_value_store_->Put(OatHeader::kBootClassPathKey, bcp_dependency); + ArrayRef<const DexFile* const> bcp_dex_files_dependency = + bcp_dex_files.SubArray(/*pos=*/ 0u, bcp_dex_files.size() - dex_files.size()); + ArrayRef<ImageSpace* const> image_spaces(runtime->GetHeap()->GetBootImageSpaces()); + key_value_store_->Put( + OatHeader::kBootClassPathChecksumsKey, + gc::space::ImageSpace::GetBootClassPathChecksums(image_spaces, bcp_dex_files_dependency)); + } else { if (CompilerFilter::DependsOnImageChecksum(original_compiler_filter)) { TimingLogger::ScopedTiming t3("Loading image checksum", timings_); Runtime* runtime = Runtime::Current(); key_value_store_->Put(OatHeader::kBootClassPathKey, android::base::Join(runtime->GetBootClassPathLocations(), ':')); - std::vector<ImageSpace*> image_spaces = runtime->GetHeap()->GetBootImageSpaces(); - const std::vector<const DexFile*>& bcp_dex_files = - runtime->GetClassLinker()->GetBootClassPath(); + ArrayRef<ImageSpace* const> image_spaces(runtime->GetHeap()->GetBootImageSpaces()); key_value_store_->Put( OatHeader::kBootClassPathChecksumsKey, gc::space::ImageSpace::GetBootClassPathChecksums(image_spaces, bcp_dex_files)); @@ -1607,7 +1691,7 @@ class Dex2Oat final { CHECK(driver_ == nullptr); // If we use a swap file, ensure we are above the threshold to make it necessary. if (swap_fd_ != -1) { - if (!UseSwap(IsBootImage(), dex_files)) { + if (!UseSwap(IsBootImage() || IsBootImageExtension(), dex_files)) { close(swap_fd_); swap_fd_ = -1; VLOG(compiler) << "Decided to run without swap."; @@ -1624,7 +1708,7 @@ class Dex2Oat final { Thread* self = Thread::Current(); WellKnownClasses::Init(self->GetJniEnv()); - if (!IsBootImage()) { + if (!IsBootImage() && !IsBootImageExtension()) { constexpr bool kSaveDexInput = false; if (kSaveDexInput) { SaveDexInput(); @@ -1713,7 +1797,7 @@ class Dex2Oat final { if (!no_inline_filters.empty()) { std::vector<const DexFile*> class_path_files; - if (!IsBootImage()) { + if (!IsBootImage() && !IsBootImageExtension()) { // The class loader context is used only for apps. class_path_files = class_loader_context_->FlattenOpenedDexFiles(); } @@ -1758,7 +1842,7 @@ class Dex2Oat final { compiler_kind_, thread_count_, swap_fd_)); - if (!IsBootImage()) { + if (!IsBootImage() && !IsBootImageExtension()) { driver_->SetClasspathDexFiles(class_loader_context_->FlattenOpenedDexFiles()); } @@ -1806,7 +1890,7 @@ class Dex2Oat final { ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); jobject class_loader = nullptr; - if (!IsBootImage()) { + if (!IsBootImage() && !IsBootImageExtension()) { class_loader = class_loader_context_->CreateClassLoader(compiler_options_->dex_files_for_oat_file_); callbacks_->SetDexFiles(&dex_files); @@ -1950,7 +2034,7 @@ class Dex2Oat final { { TimingLogger::ScopedTiming t2("dex2oat Write VDEX", timings_); - DCHECK(IsBootImage() || oat_files_.size() == 1u); + DCHECK(IsBootImage() || IsBootImageExtension() || oat_files_.size() == 1u); verifier::VerifierDeps* verifier_deps = callbacks_->GetVerifierDeps(); for (size_t i = 0, size = oat_files_.size(); i != size; ++i) { File* vdex_file = vdex_files_[i].get(); @@ -2174,7 +2258,7 @@ class Dex2Oat final { } bool IsImage() const { - return IsAppImage() || IsBootImage(); + return IsAppImage() || IsBootImage() || IsBootImageExtension(); } bool IsAppImage() const { @@ -2185,6 +2269,10 @@ class Dex2Oat final { return compiler_options_->IsBootImage(); } + bool IsBootImageExtension() const { + return compiler_options_->IsBootImageExtension(); + } + bool IsHost() const { return is_host_; } @@ -2389,7 +2477,7 @@ class Dex2Oat final { bool PrepareRuntimeOptions(RuntimeArgumentMap* runtime_options, QuickCompilerCallbacks* callbacks) { RuntimeOptions raw_options; - if (boot_image_filename_.empty()) { + if (IsBootImage()) { std::string boot_class_path = "-Xbootclasspath:"; boot_class_path += android::base::Join(dex_filenames_, ':'); raw_options.push_back(std::make_pair(boot_class_path, nullptr)); @@ -2479,7 +2567,7 @@ class Dex2Oat final { bool CreateImageFile() REQUIRES(!Locks::mutator_lock_) { CHECK(image_writer_ != nullptr); - if (!IsBootImage()) { + if (!IsBootImage() && !IsBootImageExtension()) { CHECK(image_filenames_.empty()); image_filenames_.push_back(app_image_file_name_); } @@ -2862,7 +2950,10 @@ static dex2oat::ReturnCode Dex2oat(int argc, char** argv) { // 3) Compiling with --host // 4) Compiling on the host (not a target build) // Otherwise, print a stripped command line. - if (kIsDebugBuild || dex2oat->IsBootImage() || dex2oat->IsHost() || !kIsTargetBuild) { + if (kIsDebugBuild || + dex2oat->IsBootImage() || dex2oat->IsBootImageExtension() || + dex2oat->IsHost() || + !kIsTargetBuild) { LOG(INFO) << CommandLine(); } else { LOG(INFO) << StrippedCommandLine(); diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc index d0d50812d6..1a0a9c8892 100644 --- a/dex2oat/dex2oat_image_test.cc +++ b/dex2oat/dex2oat_image_test.cc @@ -14,31 +14,44 @@ * limitations under the License. */ +#include <fstream> #include <regex> #include <sstream> #include <string> #include <vector> +#include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> #include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> #include "common_runtime_test.h" +#include "base/array_ref.h" #include "base/file_utils.h" #include "base/macros.h" +#include "base/mem_map.h" +#include "base/string_view_cpp20.h" #include "base/unix_file/fd_file.h" #include "base/utils.h" #include "dex/art_dex_file_loader.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_loader.h" #include "dex/method_reference.h" +#include "gc/space/image_space.h" #include "profile/profile_compilation_info.h" #include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-current-inl.h" namespace art { +// A suitable address for loading the core images. +constexpr uint32_t kBaseAddress = ART_BASE_ADDRESS; + struct ImageSizes { size_t art_size = 0; size_t oat_size = 0; @@ -132,8 +145,12 @@ class Dex2oatImageTest : public CommonRuntimeTest { scratch_dir.pop_back(); } CHECK(!scratch_dir.empty()) << "No directory " << scratch.GetFilename(); + std::vector<std::string> libcore_dex_files = GetLibCoreDexFileNames(); + ArrayRef<const std::string> dex_files(libcore_dex_files); + std::vector<std::string> local_extra_args = extra_args; + local_extra_args.push_back(android::base::StringPrintf("--base=0x%08x", kBaseAddress)); std::string error_msg; - if (!CompileBootImage(extra_args, scratch.GetFilename(), &error_msg)) { + if (!CompileBootImage(local_extra_args, scratch.GetFilename(), dex_files, &error_msg)) { LOG(ERROR) << "Failed to compile image " << scratch.GetFilename() << error_msg; } std::string art_file = scratch.GetFilename() + ".art"; @@ -151,19 +168,19 @@ class Dex2oatImageTest : public CommonRuntimeTest { scratch.Close(); // Clear image files since we compile the image multiple times and don't want to leave any // artifacts behind. - ClearDirectory(scratch_dir.c_str(), /*recursive*/ false); + ClearDirectory(scratch_dir.c_str(), /*recursive=*/ false); return ret; } bool CompileBootImage(const std::vector<std::string>& extra_args, const std::string& image_file_name_prefix, + ArrayRef<const std::string> dex_files, std::string* error_msg) { Runtime* const runtime = Runtime::Current(); std::vector<std::string> argv; argv.push_back(runtime->GetCompilerExecutable()); AddRuntimeArg(argv, "-Xms64m"); AddRuntimeArg(argv, "-Xmx64m"); - std::vector<std::string> dex_files = GetLibCoreDexFileNames(); for (const std::string& dex_file : dex_files) { argv.push_back("--dex-file=" + dex_file); argv.push_back("--dex-location=" + dex_file); @@ -182,7 +199,6 @@ class Dex2oatImageTest : public CommonRuntimeTest { argv.push_back("--image=" + image_file_name_prefix + ".art"); argv.push_back("--oat-file=" + image_file_name_prefix + ".oat"); argv.push_back("--oat-location=" + image_file_name_prefix + ".oat"); - argv.push_back("--base=0x60000000"); std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); @@ -265,4 +281,266 @@ TEST_F(Dex2oatImageTest, TestModesAndFilters) { } } +TEST_F(Dex2oatImageTest, TestExtension) { + constexpr size_t kReservationSize = 256 * MB; // This should be enough for the compiled images. + // Extend to both directions for maximum relocation difference. + static_assert(ART_BASE_ADDRESS_MIN_DELTA < 0); + static_assert(ART_BASE_ADDRESS_MAX_DELTA > 0); + static_assert(IsAligned<kPageSize>(ART_BASE_ADDRESS_MIN_DELTA)); + static_assert(IsAligned<kPageSize>(ART_BASE_ADDRESS_MAX_DELTA)); + constexpr size_t kExtra = ART_BASE_ADDRESS_MAX_DELTA - ART_BASE_ADDRESS_MIN_DELTA; + uint32_t min_relocated_address = kBaseAddress + ART_BASE_ADDRESS_MIN_DELTA; + std::string error_msg; + MemMap reservation = MemMap::MapAnonymous("Reservation", + reinterpret_cast<uint8_t*>(min_relocated_address), + kReservationSize + kExtra, + PROT_NONE, + /*low_4gb=*/ true, + /*reuse=*/ false, + /*reservation=*/ nullptr, + &error_msg); + ASSERT_TRUE(reservation.IsValid()); + + ScratchFile scratch; + std::string scratch_dir = scratch.GetFilename() + "-d"; + int mkdir_result = mkdir(scratch_dir.c_str(), 0700); + ASSERT_EQ(0, mkdir_result); + scratch_dir += '/'; + std::string image_dir = scratch_dir + GetInstructionSetString(kRuntimeISA); + mkdir_result = mkdir(image_dir.c_str(), 0700); + ASSERT_EQ(0, mkdir_result); + std::string filename_prefix = image_dir + "/core"; + + // Copy the libcore dex files to a custom dir inside `scratch_dir` so that we do not + // accidentally load pre-compiled core images from their original directory based on BCP paths. + std::string jar_dir = scratch_dir + "jars"; + mkdir_result = mkdir(jar_dir.c_str(), 0700); + ASSERT_EQ(0, mkdir_result); + jar_dir += '/'; + std::vector<std::string> libcore_dex_files = GetLibCoreDexFileNames(); + for (std::string& dex_file : libcore_dex_files) { + size_t slash_pos = dex_file.rfind('/'); + ASSERT_NE(std::string::npos, slash_pos); + std::string new_location = jar_dir + dex_file.substr(slash_pos + 1u); + std::ifstream src_stream(dex_file, std::ios::binary); + std::ofstream dst_stream(new_location, std::ios::binary); + dst_stream << src_stream.rdbuf(); + dex_file = new_location; + } + + ArrayRef<const std::string> full_bcp(libcore_dex_files); + size_t total_dex_files = full_bcp.size(); + ASSERT_GE(total_dex_files, 4u); // 2 for "head", 1 for "tail", at least one for "mid", see below. + + // The primary image must contain at least core-oj and core-libart to initialize the runtime. + ASSERT_NE(std::string::npos, full_bcp[0].find("core-oj")); + ASSERT_NE(std::string::npos, full_bcp[1].find("core-libart")); + ArrayRef<const std::string> head_dex_files = full_bcp.SubArray(/*pos=*/ 0u, /*length=*/ 2u); + // Middle part is everything else except for conscrypt. + ASSERT_NE(std::string::npos, full_bcp[full_bcp.size() - 1u].find("conscrypt")); + ArrayRef<const std::string> mid_bcp = + full_bcp.SubArray(/*pos=*/ 0u, /*length=*/ total_dex_files - 1u); + ArrayRef<const std::string> mid_dex_files = mid_bcp.SubArray(/*pos=*/ 2u); + // Tail is just the conscrypt. + ArrayRef<const std::string> tail_dex_files = + full_bcp.SubArray(/*pos=*/ total_dex_files - 1u, /*length=*/ 1u); + + // Prepare the "head", "mid" and "tail" names and locations. + std::string base_name = "core.art"; + std::string base_location = scratch_dir + base_name; + std::vector<std::string> expanded_mid = gc::space::ImageSpace::ExpandMultiImageLocations( + mid_dex_files.SubArray(/*pos=*/ 0u, /*length=*/ 1u), + base_location, + /*boot_image_extension=*/ true); + CHECK_EQ(1u, expanded_mid.size()); + std::string mid_location = expanded_mid[0]; + size_t mid_slash_pos = mid_location.rfind('/'); + ASSERT_NE(std::string::npos, mid_slash_pos); + std::string mid_name = mid_location.substr(mid_slash_pos + 1u); + CHECK_EQ(1u, tail_dex_files.size()); + std::vector<std::string> expanded_tail = gc::space::ImageSpace::ExpandMultiImageLocations( + tail_dex_files, base_location, /*boot_image_extension=*/ true); + CHECK_EQ(1u, expanded_tail.size()); + std::string tail_location = expanded_tail[0]; + size_t tail_slash_pos = tail_location.rfind('/'); + ASSERT_NE(std::string::npos, tail_slash_pos); + std::string tail_name = tail_location.substr(tail_slash_pos + 1u); + + // Compile the "head", i.e. the primary boot image. + std::string base = android::base::StringPrintf("--base=0x%08x", kBaseAddress); + bool head_ok = CompileBootImage({base}, filename_prefix, head_dex_files, &error_msg); + ASSERT_TRUE(head_ok) << error_msg; + + // Compile the "mid", i.e. the first extension. + std::string mid_bcp_string = android::base::Join(mid_bcp, ':'); + std::vector<std::string> extra_args; + AddRuntimeArg(extra_args, "-Xbootclasspath:" + mid_bcp_string); + AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + mid_bcp_string); + extra_args.push_back("--boot-image=" + base_location); + bool mid_ok = CompileBootImage(extra_args, filename_prefix, mid_dex_files, &error_msg); + ASSERT_TRUE(mid_ok) << error_msg; + + // Try to compile the "tail" without specifying the "mid" extension. This shall fail. + std::string full_bcp_string = android::base::Join(full_bcp, ':'); + extra_args.clear(); + AddRuntimeArg(extra_args, "-Xbootclasspath:" + full_bcp_string); + AddRuntimeArg(extra_args, "-Xbootclasspath-locations:" + full_bcp_string); + extra_args.push_back("--boot-image=" + base_location); + bool tail_ok = CompileBootImage(extra_args, filename_prefix, tail_dex_files, &error_msg); + ASSERT_FALSE(tail_ok) << error_msg; + + // Now compile the tail against both "head" and "mid". + CHECK(StartsWith(extra_args.back(), "--boot-image=")); + extra_args.back() = "--boot-image=" + base_location + ':' + mid_location; + tail_ok = CompileBootImage(extra_args, filename_prefix, tail_dex_files, &error_msg); + ASSERT_TRUE(tail_ok) << error_msg; + + reservation = MemMap::Invalid(); // Free the reserved memory for loading images. + + // Try to load the boot image with different image locations. + std::vector<std::string> boot_class_path = libcore_dex_files; + std::vector<std::unique_ptr<gc::space::ImageSpace>> boot_image_spaces; + bool relocate = false; + MemMap extra_reservation; + auto load = [&](const std::string& image_location) { + boot_image_spaces.clear(); + extra_reservation = MemMap::Invalid(); + ScopedObjectAccess soa(Thread::Current()); + return gc::space::ImageSpace::LoadBootImage(/*boot_class_path=*/ boot_class_path, + /*boot_class_path_locations=*/ libcore_dex_files, + image_location, + kRuntimeISA, + gc::space::ImageSpaceLoadingOrder::kSystemFirst, + relocate, + /*executable=*/ true, + /*is_zygote=*/ false, + /*extra_reservation_size=*/ 0u, + &boot_image_spaces, + &extra_reservation); + }; + + for (bool r : { false, true}) { + relocate = r; + + // Load primary image with full path. + bool load_ok = load(base_location); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_FALSE(extra_reservation.IsValid()); + ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size()); + + // Fail to load primary image with just the name. + load_ok = load(base_name); + ASSERT_FALSE(load_ok); + + // Fail to load primary image with a search path. + load_ok = load("*"); + ASSERT_FALSE(load_ok); + load_ok = load(scratch_dir + "*"); + ASSERT_FALSE(load_ok); + + // Load the primary and first extension with full path. + load_ok = load(base_location + ':' + mid_location); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size()); + + // Load the primary with full path and fail to load first extension without full path. + load_ok = load(base_location + ':' + mid_name); + ASSERT_TRUE(load_ok) << error_msg; // Primary image loaded successfully. + ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size()); // But only the primary image. + + // Load all the libcore images with full paths. + load_ok = load(base_location + ':' + mid_location + ':' + tail_location); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); + + // Load the primary and first extension with full paths, fail to load second extension by name. + load_ok = load(base_location + ':' + mid_location + ':' + tail_name); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size()); + + // Load the primary with full path and fail to load first extension without full path, + // fail to load second extension because it depends on the first. + load_ok = load(base_location + ':' + mid_name + ':' + tail_location); + ASSERT_TRUE(load_ok) << error_msg; // Primary image loaded successfully. + ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size()); // But only the primary image. + + // Load the primary with full path and extensions with a specified search path. + load_ok = load(base_location + ':' + scratch_dir + '*'); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); + + // Load the primary with full path and fail to find extensions in BCP path. + load_ok = load(base_location + ":*"); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(head_dex_files.size(), boot_image_spaces.size()); + } + + // Now copy the libcore dex files to the `scratch_dir` and retry loading the boot image + // with BCP in the scratch_dir so that the images can be found based on BCP paths. + for (std::string& bcp_component : boot_class_path) { + size_t slash_pos = bcp_component.rfind('/'); + ASSERT_NE(std::string::npos, slash_pos); + std::string new_location = scratch_dir + bcp_component.substr(slash_pos + 1u); + std::ifstream src_stream(bcp_component, std::ios::binary); + std::ofstream dst_stream(new_location, std::ios::binary); + dst_stream << src_stream.rdbuf(); + bcp_component = new_location; + } + + for (bool r : { false, true}) { + relocate = r; + + // Loading the primary image with just the name now succeeds. + bool load_ok = load(base_name); + ASSERT_TRUE(load_ok) << error_msg; + + // Loading the primary image with a search path still fails. + load_ok = load("*"); + ASSERT_FALSE(load_ok); + load_ok = load(scratch_dir + "*"); + ASSERT_FALSE(load_ok); + + // Load the primary and first extension without paths. + load_ok = load(base_name + ':' + mid_name); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size()); + + // Load the primary with full path and the first extension without full path. + load_ok = load(base_location + ':' + mid_name); + ASSERT_TRUE(load_ok) << error_msg; // Loaded successfully. + ASSERT_EQ(mid_bcp.size(), boot_image_spaces.size()); // Including the extension. + + // Load all the libcore images without paths. + load_ok = load(base_name + ':' + mid_name + ':' + tail_name); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); + + // Load the primary and first extension with full paths and second extension by name. + load_ok = load(base_location + ':' + mid_location + ':' + tail_name); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); + + // Load the primary with full path, first extension without path, + // and second extension with full path. + load_ok = load(base_location + ':' + mid_name + ':' + tail_location); + ASSERT_TRUE(load_ok) << error_msg; // Loaded successfully. + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); // Including both extensions. + + // Load the primary with full path and find both extensions in BCP path. + load_ok = load(base_location + ":*"); + ASSERT_TRUE(load_ok) << error_msg; + ASSERT_EQ(full_bcp.size(), boot_image_spaces.size()); + + // Fail to load any images with invalid image locations (named component after search paths). + load_ok = load(base_location + ":*:" + tail_location); + ASSERT_FALSE(load_ok); + load_ok = load(base_location + ':' + scratch_dir + "*:" + tail_location); + ASSERT_FALSE(load_ok); + } + + ClearDirectory(scratch_dir.c_str()); + int rmdir_result = rmdir(scratch_dir.c_str()); + ASSERT_EQ(0, rmdir_result); +} + } // namespace art diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index 81366e4590..bd439b72b3 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -32,6 +32,7 @@ #include "arch/instruction_set_features.h" #include "base/macros.h" #include "base/mutex-inl.h" +#include "base/string_view_cpp20.h" #include "base/utils.h" #include "dex/art_dex_file_loader.h" #include "dex/base64_test_util.h" @@ -111,13 +112,15 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { CompilerFilter::Filter filter, const std::vector<std::string>& extra_args = {}, bool expect_success = true, - bool use_fd = false) WARN_UNUSED { + bool use_fd = false, + bool use_zip_fd = false) WARN_UNUSED { return GenerateOdexForTest(dex_location, odex_location, filter, extra_args, expect_success, use_fd, + use_zip_fd, [](const OatFile&) {}); } @@ -131,9 +134,22 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { const std::vector<std::string>& extra_args, bool expect_success, bool use_fd, + bool use_zip_fd, T check_oat) WARN_UNUSED { + std::vector<std::string> dex_locations; + if (use_zip_fd) { + std::string loc_arg = "--zip-location=" + dex_location; + CHECK(std::any_of(extra_args.begin(), + extra_args.end(), + [&](const std::string& s) { return s == loc_arg; })); + CHECK(std::any_of(extra_args.begin(), + extra_args.end(), + [](const std::string& s) { return StartsWith(s, "--zip-fd="); })); + } else { + dex_locations.push_back(dex_location); + } std::string error_msg; - int status = GenerateOdexForTestWithStatus({dex_location}, + int status = GenerateOdexForTestWithStatus(dex_locations, odex_location, filter, &error_msg, @@ -1082,7 +1098,8 @@ class Dex2oatClassLoaderContextTest : public Dex2oatTest { CompilerFilter::kQuicken, extra_args, expected_success, - /*use_fd*/ false, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, check_oat)); } @@ -1602,8 +1619,9 @@ TEST_F(Dex2oatDedupeCode, DedupeTest) { base_oat_name, CompilerFilter::Filter::kSpeed, { "--deduplicate-code=false" }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [&no_dedupe_size](const OatFile& o) { no_dedupe_size = o.Size(); })); @@ -1613,8 +1631,9 @@ TEST_F(Dex2oatDedupeCode, DedupeTest) { base_oat_name, CompilerFilter::Filter::kSpeed, { "--deduplicate-code=true" }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [&dedupe_size](const OatFile& o) { dedupe_size = o.Size(); })); @@ -1630,8 +1649,9 @@ TEST_F(Dex2oatTest, UncompressedTest) { base_oat_name, CompilerFilter::Filter::kQuicken, { }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile& o) { CHECK(!o.ContainsDexCode()); })); @@ -1750,8 +1770,9 @@ TEST_F(Dex2oatTest, CompactDexGenerationFailure) { oat_filename, CompilerFilter::Filter::kVerify, { }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile& o) { CHECK(o.ContainsDexCode()); })); @@ -1882,8 +1903,9 @@ TEST_F(Dex2oatTest, DontExtract) { odex_location, CompilerFilter::Filter::kVerify, { "--copy-dex-files=false" }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile&) {})); { // Check the vdex doesn't have dex. @@ -1944,8 +1966,9 @@ TEST_F(Dex2oatTest, DontExtract) { // target. "--runtime-arg", "-Xuse-stderr-logger" }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile& o) { CHECK(o.ContainsDexCode()); })); @@ -2131,8 +2154,9 @@ TEST_F(Dex2oatTest, AppImageNoProfile) { odex_location, CompilerFilter::Filter::kSpeedProfile, { "--app-image-fd=" + std::to_string(app_image_file.GetFd()) }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile&) {})); // Open our generated oat file. std::string error_msg; @@ -2155,6 +2179,24 @@ TEST_F(Dex2oatTest, AppImageNoProfile) { EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtFields).Size(), 0u); } +TEST_F(Dex2oatTest, ZipFd) { + std::string zip_location = GetTestDexFileName("MainUncompressed"); + std::unique_ptr<File> dex_file(OS::OpenFileForReading(zip_location.c_str())); + std::vector<std::string> extra_args{ + StringPrintf("--zip-fd=%d", dex_file->Fd()), + "--zip-location=" + zip_location, + }; + std::string out_dir = GetScratchDir(); + const std::string base_oat_name = out_dir + "/base.oat"; + ASSERT_TRUE(GenerateOdexForTest(zip_location, + base_oat_name, + CompilerFilter::Filter::kQuicken, + extra_args, + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ true)); +} + TEST_F(Dex2oatTest, AppImageResolveStrings) { using Hotness = ProfileCompilationInfo::MethodHotness; // Create a profile with the startup method marked. @@ -2225,8 +2267,9 @@ TEST_F(Dex2oatTest, AppImageResolveStrings) { { "--app-image-file=" + app_image_location, "--resolve-startup-const-strings=true", "--profile-file=" + profile_file.GetFilename()}, - /* expect_success= */ true, - /* use_fd= */ false, + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [](const OatFile&) {})); // Open our generated oat file. std::string error_msg; @@ -2338,8 +2381,9 @@ TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) { odex_location, CompilerFilter::Filter::kQuicken, { "--class-loader-context=" + stored_context }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [&](const OatFile& oat_file) { EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context) << output_; EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context) << output_; @@ -2350,8 +2394,9 @@ TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) { CompilerFilter::Filter::kQuicken, { "--class-loader-context=" + valid_context, "--stored-class-loader-context=" + stored_context }, - true, // expect_success - false, // use_fd + /*expect_success=*/ true, + /*use_fd=*/ false, + /*use_zip_fd=*/ false, [&](const OatFile& oat_file) { EXPECT_EQ(oat_file.GetClassLoaderContext(), expected_stored_context) << output_; })); diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc index 1a5701dd38..33d122bdbe 100644 --- a/dex2oat/linker/image_test.cc +++ b/dex2oat/linker/image_test.cc @@ -87,6 +87,8 @@ TEST_F(ImageTest, ImageHeaderIsValid) { oat_file_end, /*boot_image_begin=*/ 0u, /*boot_image_size=*/ 0u, + /*boot_image_component_count=*/ 0u, + /*boot_image_checksum=*/ 0u, sizeof(void*)); ASSERT_TRUE(image_header.IsValid()); diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 9e5da2774c..9ec6e56922 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -48,6 +48,7 @@ #include "linker/multi_oat_relative_patcher.h" #include "lock_word.h" #include "mirror/object-inl.h" +#include "oat.h" #include "oat_writer.h" #include "scoped_thread_state_change-inl.h" #include "signal_catcher.h" @@ -171,8 +172,9 @@ inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode, // Create a generic tmp file, to be the base of the .art and .oat temporary files. ScratchFile location; std::vector<std::string> image_locations = - gc::space::ImageSpace::ExpandMultiImageLocations(out_helper.dex_file_locations, - location.GetFilename() + ".art"); + gc::space::ImageSpace::ExpandMultiImageLocations( + ArrayRef<const std::string>(out_helper.dex_file_locations), + location.GetFilename() + ".art"); for (size_t i = 0u; i != class_path.size(); ++i) { out_helper.image_locations.push_back(ScratchFile(image_locations[i])); } diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc index ede5ef7704..eb87b82565 100644 --- a/dex2oat/linker/image_writer.cc +++ b/dex2oat/linker/image_writer.cc @@ -2653,6 +2653,22 @@ void ImageWriter::CreateHeader(size_t oat_index) { } } + // Compute boot image checksums for the primary component, leave as 0 otherwise. + uint32_t boot_image_components = 0u; + uint32_t boot_image_checksums = 0u; + if (oat_index == 0u) { + const std::vector<gc::space::ImageSpace*>& image_spaces = + Runtime::Current()->GetHeap()->GetBootImageSpaces(); + boot_image_components = dchecked_integral_cast<uint32_t>(image_spaces.size()); + DCHECK_EQ(boot_image_components == 0u, compiler_options_.IsBootImage()); + for (uint32_t i = 0; i != boot_image_components; ) { + const ImageHeader& header = image_spaces[i]->GetImageHeader(); + boot_image_checksums ^= header.GetImageChecksum(); + DCHECK_LE(header.GetComponentCount(), boot_image_components - i); + i += header.GetComponentCount(); + } + } + // Create the image sections. auto section_info_pair = image_info.CreateImageSections(); const size_t image_end = section_info_pair.first; @@ -2695,6 +2711,8 @@ void ImageWriter::CreateHeader(size_t oat_index) { PointerToLowMemUInt32(oat_file_end), boot_image_begin_, boot_image_size_, + boot_image_components, + boot_image_checksums, static_cast<uint32_t>(target_ptr_size_)); } diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index 4183dd9ad7..92de151416 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -61,6 +61,7 @@ #include "mirror/class_loader.h" #include "mirror/dex_cache-inl.h" #include "mirror/object-inl.h" +#include "oat.h" #include "oat_quick_method_header.h" #include "profile/profile_compilation_info.h" #include "quicken_info.h" @@ -1611,7 +1612,7 @@ class OatWriter::InitImageMethodVisitor : public OatDexMethodVisitor { // Assign a pointer to quick code for copied methods // not handled in the method StartClass - void Postprocess() { + void Postprocess() REQUIRES_SHARED(Locks::mutator_lock_) { for (std::pair<ArtMethod*, ArtMethod*>& p : methods_to_process_) { ArtMethod* method = p.first; ArtMethod* origin = p.second; @@ -2271,6 +2272,7 @@ size_t OatWriter::InitOatCodeDexFiles(size_t offset) { } if (HasImage()) { + ScopedObjectAccess soa(Thread::Current()); ScopedAssertNoThreadSuspension sants("Init image method visitor", Thread::Current()); InitImageMethodVisitor image_visitor(this, offset, dex_files_); success = VisitDexMethods(&image_visitor); diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h index fb263d9cd9..19c0cfefa2 100644 --- a/dex2oat/linker/oat_writer.h +++ b/dex2oat/linker/oat_writer.h @@ -35,7 +35,6 @@ #include "dex/type_reference.h" #include "linker/relative_patcher.h" // For RelativePatcherTargetProvider. #include "mirror/class.h" -#include "oat.h" namespace art { @@ -44,6 +43,7 @@ class CompiledMethod; class CompilerDriver; class CompilerOptions; class DexContainer; +class OatHeader; class OutputStream; class ProfileCompilationInfo; class TimingLogger; diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index 2eb4476667..11600a8e01 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -41,6 +41,7 @@ #include "mirror/class-inl.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" +#include "oat.h" #include "oat_file-inl.h" #include "oat_writer.h" #include "profile/profile_compilation_info.h" diff --git a/dex2oat/linker/relative_patcher_test.h b/dex2oat/linker/relative_patcher_test.h index dc53ac4321..f34e6eb059 100644 --- a/dex2oat/linker/relative_patcher_test.h +++ b/dex2oat/linker/relative_patcher_test.h @@ -29,7 +29,6 @@ #include "dex/string_reference.h" #include "driver/compiled_method_storage.h" #include "linker/relative_patcher.h" -#include "oat.h" #include "oat_quick_method_header.h" #include "stream/vector_output_stream.h" diff --git a/libartbase/Android.bp b/libartbase/Android.bp index fc63f54e5d..d8cf12366b 100644 --- a/libartbase/Android.bp +++ b/libartbase/Android.bp @@ -168,8 +168,6 @@ art_cc_library { defaults: ["libartbase_defaults"], visibility: [ // TODO(b/133140750): Clean this up. - "//cts/tests/tests/appop", - "//frameworks/base/startop/view_compiler", "//packages/modules/NetworkStack/tests:__subpackages__", ], diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h index 3b85e38cf8..a9a903b71f 100644 --- a/libartbase/base/hiddenapi_flags.h +++ b/libartbase/base/hiddenapi_flags.h @@ -193,6 +193,7 @@ class ApiList { static ApiList Blacklist() { return ApiList(Value::kBlacklist); } static ApiList GreylistMaxO() { return ApiList(Value::kGreylistMaxO); } static ApiList GreylistMaxP() { return ApiList(Value::kGreylistMaxP); } + static ApiList GreylistMaxQ() { return ApiList(Value::kGreylistMaxQ); } static ApiList CorePlatformApi() { return ApiList(DomainApi::kCorePlatformApi); } static ApiList TestApi() { return ApiList(DomainApi::kTestApi); } @@ -289,6 +290,16 @@ class ApiList { // Returns true when no ApiList is specified and no domain_api flags either. bool IsEmpty() const { return (GetValue() == Value::kInvalid) && (GetDomainApis() == 0); } + // Returns true if the ApiList is on blacklist. + bool IsBlacklisted() const { + return GetValue() == Value::kBlacklist; + } + + // Returns true if the ApiList is a test API. + bool IsTestApi() const { + return helper::MatchesBitMask(helper::ToBit(DomainApi::kTestApi), dex_flags_); + } + // Returns the maximum target SDK version allowed to access this ApiList. SdkVersion GetMaxAllowedSdkVersion() const { return kMaxSdkVersions[GetIntValue()]; } diff --git a/libartbase/base/memfd.h b/libartbase/base/memfd.h index 53cfe9cda1..0bb336d45a 100644 --- a/libartbase/base/memfd.h +++ b/libartbase/base/memfd.h @@ -17,8 +17,46 @@ #ifndef ART_LIBARTBASE_BASE_MEMFD_H_ #define ART_LIBARTBASE_BASE_MEMFD_H_ +#include <fcntl.h> +#include <unistd.h> + #if defined(__BIONIC__) #include <linux/memfd.h> // To access memfd flags. +#else + +// If memfd flags don't exist in the current toolchain, define them ourselves. +#ifndef F_ADD_SEALS +# define F_ADD_SEALS (1033) +#endif + +#ifndef F_GET_SEALS +# define F_GET_SEALS (1034) +#endif + +#ifndef F_SEAL_SEAL +# define F_SEAL_SEAL 0x0001 +#endif + +#ifndef F_SEAL_SHRINK +# define F_SEAL_SHRINK 0x0002 +#endif + +#ifndef F_SEAL_GROW +# define F_SEAL_GROW 0x0004 +#endif + +#ifndef F_SEAL_WRITE +# define F_SEAL_WRITE 0x0008 +#endif + +#ifndef F_SEAL_FUTURE_WRITE +# define F_SEAL_FUTURE_WRITE 0x0010 +#endif + +#ifndef MFD_ALLOW_SEALING +# define MFD_ALLOW_SEALING 0x0002U +#endif + #endif namespace art { diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc index a0884340cb..7d4dd59530 100644 --- a/libartbase/base/utils.cc +++ b/libartbase/base/utils.cc @@ -16,6 +16,7 @@ #include "utils.h" +#include <dirent.h> #include <inttypes.h> #include <pthread.h> #include <sys/stat.h> @@ -355,4 +356,22 @@ bool IsAddressKnownBackedByFileOrShared(const void* addr) { return (flags & (1LL << 61)) != 0; } +int GetTaskCount() { + DIR* directory = opendir("/proc/self/task"); + if (directory == nullptr) { + return -1; + } + + uint32_t count = 0; + struct dirent* entry = nullptr; + while ((entry = readdir(directory)) != nullptr) { + if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0)) { + continue; + } + ++count; + } + closedir(directory); + return count; +} + } // namespace art diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h index 6fc537af03..4bcb9151b7 100644 --- a/libartbase/base/utils.h +++ b/libartbase/base/utils.h @@ -159,6 +159,9 @@ std::string GetProcessStatus(const char* key); // following accesses repopulate the memory or return zero. bool IsAddressKnownBackedByFileOrShared(const void* addr); +// Returns the number of threads running. +int GetTaskCount(); + } // namespace art #endif // ART_LIBARTBASE_BASE_UTILS_H_ diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp index ef140168db..0a45b8880b 100644 --- a/libartpalette/Android.bp +++ b/libartpalette/Android.bp @@ -63,10 +63,6 @@ art_cc_library { art_cc_library { name: "libartpalette", defaults: ["libartpalette_defaults"], - visibility: [ - // TODO(b/133140750): Clean this up. - "//frameworks/base/startop/view_compiler", - ], required: ["libartpalette-system"], // libartpalette.so dlopen()'s libartpalette-system. header_libs: ["libbase_headers"], target: { diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp index ffb616c31c..a08f89faa6 100644 --- a/libdexfile/Android.bp +++ b/libdexfile/Android.bp @@ -110,8 +110,6 @@ cc_defaults { ], defaults_visibility: [ "//art:__subpackages__", - // TODO(b/133140750): Clean this up. - "//frameworks/base/startop/view_compiler", ], static_libs: ["libdexfile"], } @@ -143,11 +141,6 @@ gensrcs { art_cc_library { name: "libdexfile", defaults: ["libdexfile_defaults"], - visibility: [ - // TODO(b/133140750): Clean this up. - "//cts/tests/tests/appop", - "//frameworks/base/startop/view_compiler", - ], // Leave the symbols in the shared library so that stack unwinders can // produce meaningful name resolution. strip: { diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h index 918feb34f4..bdb3781c75 100644 --- a/libdexfile/dex/modifiers.h +++ b/libdexfile/dex/modifiers.h @@ -62,6 +62,8 @@ static constexpr uint32_t kAccObsoleteObject = 0x00200000; // class (run // that it was copied from its declaring class into another class. All methods marked kAccMiranda // and kAccDefaultConflict will have this bit set. Any kAccDefault method contained in the methods_ // array of a concrete class will also have this bit set. +// We need copies of the original method because the method may end up in +// different places in classes vtables, and the vtable index is set in ArtMethod.method_index. static constexpr uint32_t kAccCopied = 0x00100000; // method (runtime) static constexpr uint32_t kAccMiranda = 0x00200000; // method (runtime, not native) static constexpr uint32_t kAccDefault = 0x00400000; // method (runtime) diff --git a/libelffile/elf/elf_builder.h b/libelffile/elf/elf_builder.h index b528f6a221..07f0d00b23 100644 --- a/libelffile/elf/elf_builder.h +++ b/libelffile/elf/elf_builder.h @@ -746,13 +746,13 @@ class ElfBuilder final { hash_.AllocateVirtualMemory(hash_.GetCacheSize()); Elf_Dyn dyns[] = { - { .d_tag = DT_HASH, .d_un.d_ptr = hash_.GetAddress() }, - { .d_tag = DT_STRTAB, .d_un.d_ptr = dynstr_.GetAddress() }, - { .d_tag = DT_SYMTAB, .d_un.d_ptr = dynsym_.GetAddress() }, - { .d_tag = DT_SYMENT, .d_un.d_ptr = sizeof(Elf_Sym) }, - { .d_tag = DT_STRSZ, .d_un.d_ptr = dynstr_.GetCacheSize() }, - { .d_tag = DT_SONAME, .d_un.d_ptr = soname_offset }, - { .d_tag = DT_NULL, .d_un.d_ptr = 0 }, + { .d_tag = DT_HASH, .d_un = { .d_ptr = hash_.GetAddress() }, }, + { .d_tag = DT_STRTAB, .d_un = { .d_ptr = dynstr_.GetAddress() }, }, + { .d_tag = DT_SYMTAB, .d_un = { .d_ptr = dynsym_.GetAddress() }, }, + { .d_tag = DT_SYMENT, .d_un = { .d_ptr = sizeof(Elf_Sym) }, }, + { .d_tag = DT_STRSZ, .d_un = { .d_ptr = dynstr_.GetCacheSize() }, }, + { .d_tag = DT_SONAME, .d_un = { .d_ptr = soname_offset }, }, + { .d_tag = DT_NULL, .d_un = { .d_ptr = 0 }, }, }; dynamic_.Add(&dyns, sizeof(dyns)); dynamic_.AllocateVirtualMemory(dynamic_.GetCacheSize()); diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 22822f8b40..23f7151c99 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -90,41 +90,42 @@ class ScopedEventDispatchEnvironment final : public art::ValueObject { // Infrastructure to achieve type safety for event dispatch. -#define FORALL_EVENT_TYPES(fn) \ - fn(VMInit, ArtJvmtiEvent::kVmInit) \ - fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ - fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ - fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ - fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ - fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ - fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ - fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ - fn(VMStart, ArtJvmtiEvent::kVmStart) \ - fn(Exception, ArtJvmtiEvent::kException) \ - fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ - fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ - fn(FramePop, ArtJvmtiEvent::kFramePop) \ - fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ - fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ - fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ - fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ - fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ - fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ - fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ - fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ - fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ - fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ - fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ - fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ - fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ - fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ - fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ - fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ - fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ - fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ - fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ - fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ - fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) +#define FORALL_EVENT_TYPES(fn) \ + fn(VMInit, ArtJvmtiEvent::kVmInit) \ + fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ + fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ + fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ + fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ + fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ + fn(VMStart, ArtJvmtiEvent::kVmStart) \ + fn(Exception, ArtJvmtiEvent::kException) \ + fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ + fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ + fn(FramePop, ArtJvmtiEvent::kFramePop) \ + fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ + fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ + fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ + fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ + fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ + fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ + fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ + fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ + fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ + fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ + fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ + fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ + fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ + fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ + fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ + fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ + fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ + fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ + fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ + fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ + fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) \ + fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook) template <ArtJvmtiEvent kEvent> struct EventFnType { @@ -217,7 +218,8 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, unsigned char** new_class_data) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event"); + kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || + kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event"); DCHECK(*new_class_data == nullptr); jint current_len = class_data_len; unsigned char* current_class_data = const_cast<unsigned char*>(class_data); @@ -588,6 +590,31 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr new_class_data); } +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} + template <ArtJvmtiEvent kEvent> inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const { bool dispatch = env->event_masks.global_event_mask.Test(kEvent); diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 3f205eb6f4..56406fc81d 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -100,6 +100,9 @@ jvmtiError ArtJvmtiEventCallbacks::Set(jint index, jvmtiExtensionEvent cb) { case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk): DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb); return OK; + case static_cast<jint>(ArtJvmtiEvent::kStructuralDexFileLoadHook): + StructuralDexFileLoadHook = reinterpret_cast<ArtJvmtiEventStructuralDexFileLoadHook>(cb); + return OK; default: return ERR(ILLEGAL_ARGUMENT); } @@ -116,6 +119,7 @@ bool IsExtensionEvent(ArtJvmtiEvent e) { switch (e) { case ArtJvmtiEvent::kDdmPublishChunk: case ArtJvmtiEvent::kObsoleteObjectCreated: + case ArtJvmtiEvent::kStructuralDexFileLoadHook: return true; default: return false; @@ -1175,6 +1179,7 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread) case ArtJvmtiEvent::kClassFileLoadHookRetransformable: case ArtJvmtiEvent::kDdmPublishChunk: case ArtJvmtiEvent::kObsoleteObjectCreated: + case ArtJvmtiEvent::kStructuralDexFileLoadHook: return DeoptRequirement::kNone; } } diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index d5ab4fbc98..c9d587af94 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -81,7 +81,8 @@ enum class ArtJvmtiEvent : jint { kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1, kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2, kObsoleteObjectCreated = JVMTI_MAX_EVENT_TYPE_VAL + 3, - kMaxNormalEventTypeVal = kObsoleteObjectCreated, + kStructuralDexFileLoadHook = JVMTI_MAX_EVENT_TYPE_VAL + 4, + kMaxNormalEventTypeVal = kStructuralDexFileLoadHook, // All that follow are events used to implement internal JVMTI functions. They are not settable // directly by agents. @@ -107,6 +108,17 @@ using ArtJvmtiEventObsoleteObjectCreated = void (*)(jvmtiEnv *jvmti_env, jlong* obsolete_tag, jlong* new_tag); +using ArtJvmtiEventStructuralDexFileLoadHook = void (*)(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint dex_data_len, + const unsigned char* dex_data, + jint* new_dex_data_len, + unsigned char** new_dex_data); + // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the // thread id. // Note: We could just use the tid like tracing does. @@ -119,7 +131,10 @@ struct UniqueThreadHasher { }; struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { - ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) { + ArtJvmtiEventCallbacks() + : DdmPublishChunk(nullptr), + ObsoleteObjectCreated(nullptr), + StructuralDexFileLoadHook(nullptr) { memset(this, 0, sizeof(jvmtiEventCallbacks)); } @@ -131,6 +146,7 @@ struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { ArtJvmtiEventDdmPublishChunk DdmPublishChunk; ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated; + ArtJvmtiEventStructuralDexFileLoadHook StructuralDexFileLoadHook; }; bool IsExtensionEvent(jint e); diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index 988274b4d7..82ce916ccd 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -204,6 +204,9 @@ struct ClassCallback : public art::ClassLoadCallback { memcpy(post_non_retransform.data(), def.GetDexData().data(), post_non_retransform.size()); } + // Call all structural transformation agents. + Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + event_handler, self, &def); // Call all retransformable agents. Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( event_handler, self, &def); diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h index 224e664459..cb0853bdb5 100644 --- a/openjdkjvmti/ti_class_definition.h +++ b/openjdkjvmti/ti_class_definition.h @@ -40,6 +40,7 @@ #include "base/array_ref.h" #include "base/mem_map.h" +#include "events.h" namespace openjdkjvmti { @@ -65,7 +66,8 @@ class ArtClassDefinition { current_dex_file_(), redefined_(false), from_class_ext_(false), - initialized_(false) {} + initialized_(false), + structural_transform_update_(false) {} void InitFirstLoad(const char* descriptor, art::Handle<art::mirror::ClassLoader> klass_loader, @@ -76,7 +78,7 @@ class ArtClassDefinition { ArtClassDefinition(ArtClassDefinition&& o) = default; ArtClassDefinition& operator=(ArtClassDefinition&& o) = default; - void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data) { + void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data, ArtJvmtiEvent event) { DCHECK(IsInitialized()); if (new_dex_data == nullptr) { return; @@ -86,10 +88,17 @@ class ArtClassDefinition { dex_data_memory_.resize(new_dex_len); memcpy(dex_data_memory_.data(), new_dex_data, new_dex_len); dex_data_ = art::ArrayRef<const unsigned char>(dex_data_memory_); + if (event == ArtJvmtiEvent::kStructuralDexFileLoadHook) { + structural_transform_update_ = true; + } } } } + bool HasStructuralChanges() const { + return structural_transform_update_; + } + art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const { DCHECK(IsInitialized()); if (redefined_) { @@ -187,6 +196,9 @@ class ArtClassDefinition { bool initialized_; + // Set if we had a new dex from the given transform type. + bool structural_transform_update_; + DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition); }; diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc index 5dc7445681..058a188630 100644 --- a/openjdkjvmti/ti_extension.cc +++ b/openjdkjvmti/ti_extension.cc @@ -30,6 +30,7 @@ #include <vector> +#include "jvmti.h" #include "ti_extension.h" #include "art_jvmti.h" @@ -45,6 +46,7 @@ #include "ti_monitor.h" #include "ti_redefine.h" #include "ti_search.h" +#include "transform.h" #include "thread-inl.h" @@ -416,7 +418,40 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env, return error; } - // StructurallyRedefineClass + // StructurallyRedefineClasses + error = add_extension( + reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses), + "com.android.art.class.structurally_redefine_classes", + "Entrypoint for structural class redefinition. Has the same signature as RedefineClasses." + " Currently this only supports adding new static fields to a class without any instance" + " fields or methods. After calling this com.android.art.structural_dex_file_load_hook" + " events will be triggered, followed by re-transformable ClassFileLoadHook events. After" + " this method completes subsequent RetransformClasses calls will use the input to this" + " function as the initial class definition.", + { + { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false }, + }, + { + ERR(CLASS_LOADER_UNSUPPORTED), + ERR(FAILS_VERIFICATION), + ERR(ILLEGAL_ARGUMENT), + ERR(INVALID_CLASS), + ERR(MUST_POSSESS_CAPABILITY), + ERR(MUST_POSSESS_CAPABILITY), + ERR(NULL_POINTER), + ERR(OUT_OF_MEMORY), + ERR(UNMODIFIABLE_CLASS), + ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), + ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED), + ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED), + ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED), + }); + if (error != ERR(NONE)) { + return error; + } + + // StructurallyRedefineClassDirect error = add_extension( reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect), "com.android.art.UNSAFE.class.structurally_redefine_class_direct", @@ -494,7 +529,7 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env, const char* id, const char* short_description, const std::vector<CParamInfo>& params) { - DCHECK(IsExtensionEvent(extension_event_index)); + DCHECK(IsExtensionEvent(extension_event_index)) << static_cast<jint>(extension_event_index); jvmtiExtensionEventInfo event_info; jvmtiError error; @@ -592,7 +627,35 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env, if (error != OK) { return error; } - + art::Runtime* runtime = art::Runtime::Current(); + if (runtime->GetJniIdType() == art::JniIdType::kIndices && + (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) { + error = add_extension( + ArtJvmtiEvent::kStructuralDexFileLoadHook, + "com.android.art.class.structural_dex_file_load_hook", + "Called during class load, after a 'RetransformClasses' call, or after a 'RedefineClasses'" + " call in order to allow the agent to modify the class. This event is called after any" + " non-can_retransform_classes ClassFileLoadHookEvents and before any" + " can_retransform_classes ClassFileLoadHookEvents. The transformations applied are" + " restricted in the same way that transformations applied via the " + " 'com.android.art.class.structurally_redefine_classes' extension function. The arguments" + " to the event are identical to the ones in the ClassFileLoadHook and have the same" + " semantics.", + { + { "jni_env", JVMTI_KIND_IN, JVMTI_TYPE_JNIENV, false }, + { "class_being_redefined", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true }, + { "loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false }, + { "name", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false }, + { "protection_domain", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true }, + { "dex_data_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false }, + { "dex_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false }, + { "new_dex_data_len", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false }, + { "new_dex_data", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, true }, + }); + } else { + LOG(INFO) << "debuggable & jni-type indices are required to implement structural " + << "class redefinition extensions."; + } // Copy into output buffer. *extension_count_ptr = ext_vector.size(); diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc index bbb2ced61a..702a1c462b 100644 --- a/openjdkjvmti/ti_heap.cc +++ b/openjdkjvmti/ti_heap.cc @@ -700,11 +700,6 @@ jvmtiError HeapUtil::IterateOverInstancesOfClass(jvmtiEnv* env, return ERR(INVALID_CLASS); } art::Handle<art::mirror::Class> filter_klass(hs.NewHandle(klass_ptr->AsClass())); - if (filter_klass->IsInterface()) { - // nothing is an 'instance' of an interface so just return without walking anything. - return OK; - } - ObjectTagTable* tag_table = ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get(); bool stop_reports = false; auto visitor = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index df25261654..50dc09c3c2 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -112,6 +112,7 @@ #include "non_debuggable_classes.h" #include "obj_ptr.h" #include "object_lock.h" +#include "reflective_value_visitor.h" #include "runtime.h" #include "runtime_globals.h" #include "stack.h" @@ -376,7 +377,7 @@ jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/ std::strin return ERR(INVALID_CLASS); } art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass())); - return Redefiner::GetClassRedefinitionError(h_klass, error_msg); + return Redefiner::GetClassRedefinitionError<kType>(h_klass, error_msg); } template <RedefinitionType kType> @@ -574,9 +575,10 @@ Redefiner::ClassRedefinition::~ClassRedefinition() { } } -jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, - jint class_count, - const jvmtiClassDefinition* definitions) { +template<RedefinitionType kType> +jvmtiError Redefiner::RedefineClassesGeneric(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { art::Runtime* runtime = art::Runtime::Current(); art::Thread* self = art::Thread::Current(); ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); @@ -597,7 +599,8 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, std::vector<ArtClassDefinition> def_vector; def_vector.reserve(class_count); for (jint i = 0; i < class_count; i++) { - jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, &error_msg); + jvmtiError res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>( + definitions[i].klass, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg; return res; @@ -611,15 +614,35 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, def_vector.push_back(std::move(def)); } // Call all the transformation events. - Transformer::RetransformClassesDirect(self, &def_vector); - jvmtiError res = RedefineClassesDirect( - env, runtime, self, def_vector, RedefinitionType::kNormal, &error_msg); + Transformer::RetransformClassesDirect<kType>(self, &def_vector); + if (kType == RedefinitionType::kStructural) { + Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(self, &def_vector); + } + jvmtiError res = RedefineClassesDirect(env, runtime, self, def_vector, kType, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg; } return res; } +jvmtiError Redefiner::StructurallyRedefineClasses(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { + ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); + if (art_env == nullptr) { + return ERR(INVALID_ENVIRONMENT); + } else if (art_env->capabilities.can_redefine_classes != 1) { + return ERR(MUST_POSSESS_CAPABILITY); + } + return RedefineClassesGeneric<RedefinitionType::kStructural>(jenv, class_count, definitions); +} + +jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv, + jint class_count, + const jvmtiClassDefinition* definitions) { + return RedefineClassesGeneric<RedefinitionType::kNormal>(jenv, class_count, definitions); +} + jvmtiError Redefiner::StructurallyRedefineClassDirect(jvmtiEnv* env, jclass klass, const unsigned char* data, @@ -663,6 +686,11 @@ jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, // We don't actually need to do anything. Just return OK. return OK; } + // We need to fiddle with the verification class flags. To do this we need to make sure there are + // no concurrent redefinitions of the same class at the same time. For simplicity and because + // this is not expected to be a common occurrence we will just wrap the whole thing in a TOP-level + // lock. + // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we // are going to redefine. // TODO We should prevent user-code suspensions to make sure this isn't held for too long. @@ -1149,13 +1177,10 @@ bool Redefiner::ClassRedefinition::CheckRedefinable() { art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); jvmtiError res; - switch (driver_->type_) { - case RedefinitionType::kNormal: - res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err); - break; - case RedefinitionType::kStructural: + if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) { res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err); - break; + } else { + res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err); } if (res != OK) { RecordFailure(res, err); @@ -1166,7 +1191,7 @@ bool Redefiner::ClassRedefinition::CheckRedefinable() { } bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() { - return CheckRedefinable() && CheckClass() && CheckFields() && CheckMethods(); + return CheckClass() && CheckFields() && CheckMethods() && CheckRedefinable(); } class RedefinitionDataIter; @@ -1616,6 +1641,11 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( } cur_data->SetNewClassObject(nc.Get()); + // We really want to be able to resolve to the new class-object using this dex-cache for + // verification work. Since we haven't put it in the class-table yet we wll just manually add it + // to the dex-cache. + // TODO: We should maybe do this in a better spot. + cur_data->GetNewDexCache()->SetResolvedType(nc->GetDexTypeIndex(), nc.Get()); } return true; } @@ -1863,31 +1893,33 @@ jvmtiError Redefiner::Run() { } UnregisterAllBreakpoints(); - // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done - // allocating so no deadlocks. - ScopedDisableConcurrentAndMovingGc sdcamgc(runtime_->GetHeap(), self_); - - // Do transition to final suspension - // TODO We might want to give this its own suspended state! - // TODO This isn't right. We need to change state without any chance of suspend ideally! - art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative); - art::ScopedSuspendAll ssa("Final installation of redefined Classes!", /*long_suspend=*/true); - for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) { - art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition"); - ClassRedefinition& redef = data.GetRedefinition(); - if (data.GetSourceClassLoader() != nullptr) { - ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie()); + { + // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done + // allocating so no deadlocks. + ScopedDisableConcurrentAndMovingGc sdcamgc(runtime_->GetHeap(), self_); + + // Do transition to final suspension + // TODO We might want to give this its own suspended state! + // TODO This isn't right. We need to change state without any chance of suspend ideally! + art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative); + art::ScopedSuspendAll ssa("Final installation of redefined Classes!", /*long_suspend=*/true); + for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) { + art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition"); + ClassRedefinition& redef = data.GetRedefinition(); + if (data.GetSourceClassLoader() != nullptr) { + ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie()); + } + redef.UpdateClass(data); } - redef.UpdateClass(data); - } - RestoreObsoleteMethodMapsIfUnneeded(holder); - // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any - // are, force a full-world deoptimization before finishing redefinition. If we don't do this then - // methods that have been jitted prior to the current redefinition being applied might continue - // to use the old versions of the intrinsics! - // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really - // owns the DexFile and when ownership is transferred. - ReleaseAllDexFiles(); + RestoreObsoleteMethodMapsIfUnneeded(holder); + // TODO We should check for if any of the redefined methods are intrinsic methods here and, if + // any are, force a full-world deoptimization before finishing redefinition. If we don't do this + // then methods that have been jitted prior to the current redefinition being applied might + // continue to use the old versions of the intrinsics! + // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really + // owns the DexFile and when ownership is transferred. + ReleaseAllDexFiles(); + } // By now the class-linker knows about all the classes so we can safetly retry verification and // update method flags. ReverifyClasses(holder); @@ -1895,7 +1927,6 @@ jvmtiError Redefiner::Run() { } void Redefiner::ReverifyClasses(RedefinitionDataHolder& holder) { - art::ScopedAssertNoThreadSuspension nts("Updating method flags"); for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) { data.GetRedefinition().ReverifyClass(data); } @@ -2062,25 +2093,84 @@ void Redefiner::ClassRedefinition::UpdateClassStructurally(const RedefinitionDat replacement->SetLockWord(orig->GetLockWord(false), false); orig->SetLockWord(art::LockWord::Default(), false); // Update live pointers in ART code. + auto could_change_resolution_of = [&](auto* field_or_method, + const auto& info) REQUIRES(art::Locks::mutator_lock_) { + constexpr bool is_method = std::is_same_v<art::ArtMethod*, decltype(field_or_method)>; + static_assert(is_method || std::is_same_v<art::ArtField*, decltype(field_or_method)>, + "Input is not field or method!"); + // Only dex-cache is used for resolution + if (LIKELY(info.GetType() != art::ReflectionSourceType::kSourceDexCacheResolvedField && + info.GetType() != art::ReflectionSourceType::kSourceDexCacheResolvedMethod)) { + return false; + } + if constexpr (is_method) { + // Only direct methods are used without further indirection through a vtable/IFTable. + // Constructors cannot be shadowed. + if (LIKELY(!field_or_method->IsDirect() || field_or_method->IsConstructor())) { + return false; + } + } else { + // Only non-private fields can be shadowed in a manner that's visible. + if (LIKELY(field_or_method->IsPrivate())) { + return false; + } + } + // We can only shadow things from our superclasses + if (LIKELY(!field_or_method->GetDeclaringClass()->IsAssignableFrom(orig))) { + return false; + } + if constexpr (is_method) { + auto direct_methods = replacement->GetDirectMethods(art::kRuntimePointerSize); + return std::find_if(direct_methods.begin(), + direct_methods.end(), + [&](art::ArtMethod& m) REQUIRES(art::Locks::mutator_lock_) { + return UNLIKELY(m.HasSameNameAndSignature(field_or_method)); + }) != direct_methods.end(); + } else { + auto pred = [&](art::ArtField& f) REQUIRES(art::Locks::mutator_lock_) { + return std::string_view(f.GetName()) == std::string_view(field_or_method->GetName()) && + std::string_view(f.GetTypeDescriptor()) == + std::string_view(field_or_method->GetTypeDescriptor()); + }; + if (field_or_method->IsStatic()) { + auto sfields = replacement->GetSFields(); + return std::find_if(sfields.begin(), sfields.end(), pred) != sfields.end(); + } else { + auto ifields = replacement->GetIFields(); + return std::find_if(ifields.begin(), ifields.end(), pred) != ifields.end(); + } + } + }; // TODO Performing 2 stack-walks back to back isn't the greatest. We might want to try to combine // it with the one ReplaceReferences does. Doing so would be rather complicated though. driver_->runtime_->VisitReflectiveTargets( [&](art::ArtField* f, const auto& info) REQUIRES(art::Locks::mutator_lock_) { + DCHECK(f != nullptr) << info; auto it = field_map.find(f); - if (it == field_map.end()) { - return f; + if (it != field_map.end()) { + VLOG(plugin) << "Updating " << info << " object for (field) " + << it->second->PrettyField(); + return it->second; + } else if (UNLIKELY(could_change_resolution_of(f, info))) { + // Resolution might change. Just clear the resolved value. + VLOG(plugin) << "Clearing resolution " << info << " for (field) " << f->PrettyField(); + return static_cast<art::ArtField*>(nullptr); } - VLOG(plugin) << "Updating " << info << " object for (field) " << it->second->PrettyField(); - return it->second; + return f; }, [&](art::ArtMethod* m, const auto& info) REQUIRES(art::Locks::mutator_lock_) { + DCHECK(m != nullptr) << info; auto it = method_map.find(m); - if (it == method_map.end()) { - return m; + if (it != method_map.end()) { + VLOG(plugin) << "Updating " << info << " object for (method) " + << it->second->PrettyMethod(); + return it->second; + } else if (UNLIKELY(could_change_resolution_of(m, info))) { + // Resolution might change. Just clear the resolved value. + VLOG(plugin) << "Clearing resolution " << info << " for (method) " << m->PrettyMethod(); + return static_cast<art::ArtMethod*>(nullptr); } - VLOG(plugin) << "Updating " << info << " object for (method) " - << it->second->PrettyMethod(); - return it->second; + return m; }); // Force every frame of every thread to deoptimize (any frame might have eg offsets compiled in). @@ -2208,6 +2298,25 @@ void Redefiner::ClassRedefinition::UpdateClass(const RedefinitionDataIter& holde } else { UpdateClassInPlace(holder); } + UpdateClassCommon(holder); +} + +void Redefiner::ClassRedefinition::UpdateClassCommon(const RedefinitionDataIter &cur_data) { + // NB This is after we've already replaced all old-refs with new-refs in the structural case. + art::ObjPtr<art::mirror::Class> klass(cur_data.GetMirrorClass()); + DCHECK(!IsStructuralRedefinition() || klass == cur_data.GetNewClassObject()); + if (!needs_reverify_) { + return; + } + // Force the most restrictive interpreter environment. We don't know what the final verification + // will allow. We will clear these after retrying verification once we drop the mutator-lock. + klass->VisitMethods([](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (!m->IsNative() && m->IsInvokable() && !m->IsObsolete()) { + m->ClearSkipAccessChecks(); + m->SetDontCompile(); + m->SetMustCountLocks(); + } + }, art::kRuntimePointerSize); } // Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no new diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h index dad085de59..58a688c1a0 100644 --- a/openjdkjvmti/ti_redefine.h +++ b/openjdkjvmti/ti_redefine.h @@ -87,6 +87,9 @@ class Redefiner { static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* definitions); + static jvmtiError StructurallyRedefineClasses(jvmtiEnv* env, + jint class_count, + const jvmtiClassDefinition* definitions); static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); static jvmtiError IsStructurallyModifiableClass(jvmtiEnv* env, @@ -209,9 +212,12 @@ class Redefiner { void UpdateClass(const RedefinitionDataIter& cur_data) REQUIRES(art::Locks::mutator_lock_); - void ReverifyClass(const RedefinitionDataIter& cur_data) + void UpdateClassCommon(const RedefinitionDataIter& cur_data) REQUIRES(art::Locks::mutator_lock_); + void ReverifyClass(const RedefinitionDataIter& cur_data) + REQUIRES_SHARED(art::Locks::mutator_lock_); + void CollectNewFieldAndMethodMappings(const RedefinitionDataIter& data, std::map<art::ArtMethod*, art::ArtMethod*>* method_map, std::map<art::ArtField*, art::ArtField*>* field_map) @@ -284,6 +290,11 @@ class Redefiner { REQUIRES_SHARED(art::Locks::mutator_lock_); template<RedefinitionType kType = RedefinitionType::kNormal> + static jvmtiError RedefineClassesGeneric(jvmtiEnv* env, + jint class_count, + const jvmtiClassDefinition* definitions); + + template<RedefinitionType kType = RedefinitionType::kNormal> static jvmtiError IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); template<RedefinitionType kType = RedefinitionType::kNormal> @@ -301,7 +312,7 @@ class Redefiner { bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_); void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_); - void ReverifyClasses(RedefinitionDataHolder& holder) REQUIRES(art::Locks::mutator_lock_); + void ReverifyClasses(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_); void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_); // Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no // new obsolete methods). diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc index aa37793e49..613368525e 100644 --- a/openjdkjvmti/transform.cc +++ b/openjdkjvmti/transform.cc @@ -255,13 +255,17 @@ void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookNo template void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def); +template +void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>( + EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def); template<ArtJvmtiEvent kEvent> void Transformer::TransformSingleClassDirect(EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def) { static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || - kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable, + kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || + kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "bad event type"); // We don't want to do transitions between calling the event and setting the new data so change to // native state early. This also avoids any problems that the FaultHandler might have in @@ -282,25 +286,30 @@ void Transformer::TransformSingleClassDirect(EventHandler* event_handler, dex_data.data(), /*out*/&new_len, /*out*/&new_data); - def->SetNewDexData(new_len, new_data); + def->SetNewDexData(new_len, new_data, kEvent); } +template <RedefinitionType kType> void Transformer::RetransformClassesDirect( - art::Thread* self, - /*in-out*/std::vector<ArtClassDefinition>* definitions) { + art::Thread* self, + /*in-out*/ std::vector<ArtClassDefinition>* definitions) { + constexpr ArtJvmtiEvent kEvent = kType == RedefinitionType::kNormal + ? ArtJvmtiEvent::kClassFileLoadHookRetransformable + : ArtJvmtiEvent::kStructuralDexFileLoadHook; for (ArtClassDefinition& def : *definitions) { - TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( - gEventHandler, self, &def); + TransformSingleClassDirect<kEvent>(gEventHandler, self, &def); } } +template void Transformer::RetransformClassesDirect<RedefinitionType::kNormal>( + art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); +template void Transformer::RetransformClassesDirect<RedefinitionType::kStructural>( + art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); + jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { - if (env == nullptr) { - JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM env was null!"; - return ERR(INVALID_ENVIRONMENT); - } else if (class_count < 0) { + if (class_count < 0) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM class_count was less then 0"; return ERR(ILLEGAL_ARGUMENT); } else if (class_count == 0) { @@ -317,7 +326,7 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, std::vector<ArtClassDefinition> definitions; jvmtiError res = OK; for (jint i = 0; i < class_count; i++) { - res = Redefiner::GetClassRedefinitionError(classes[i], &error_msg); + res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(classes[i], &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg; return res; @@ -330,13 +339,16 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env, } definitions.push_back(std::move(def)); } - RetransformClassesDirect(self, &definitions); - res = Redefiner::RedefineClassesDirect(ArtJvmTiEnv::AsArtJvmTiEnv(env), - runtime, - self, - definitions, - RedefinitionType::kNormal, - &error_msg); + RetransformClassesDirect<RedefinitionType::kStructural>(self, &definitions); + RetransformClassesDirect<RedefinitionType::kNormal>(self, &definitions); + RedefinitionType redef_type = + std::any_of(definitions.cbegin(), + definitions.cend(), + [](const auto& it) { return it.HasStructuralChanges(); }) + ? RedefinitionType::kStructural + : RedefinitionType::kNormal; + res = Redefiner::RedefineClassesDirect( + ArtJvmTiEnv::AsArtJvmTiEnv(env), runtime, self, definitions, redef_type, &error_msg); if (res != OK) { JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg; } diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h index 40c7267ad1..a58b50ea10 100644 --- a/openjdkjvmti/transform.h +++ b/openjdkjvmti/transform.h @@ -39,6 +39,7 @@ #include "art_jvmti.h" #include "ti_class_definition.h" +#include "ti_redefine.h" namespace openjdkjvmti { @@ -56,6 +57,7 @@ class Transformer { art::Thread* self, /*in-out*/ArtClassDefinition* def); + template<RedefinitionType kType> static void RetransformClassesDirect( art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); diff --git a/runtime/Android.bp b/runtime/Android.bp index cfa16f87dc..0546c69b41 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -497,7 +497,7 @@ gensrcs { "jdwp/jdwp_constants.h", "jni_id_type.h", "lock_word.h", - "oat.h", + "oat_file.h", "object_callbacks.h", "process_state.h", "reflective_value_visitor.h", diff --git a/runtime/art_field.h b/runtime/art_field.h index e44517e10b..bc2c399b74 100644 --- a/runtime/art_field.h +++ b/runtime/art_field.h @@ -75,6 +75,10 @@ class ArtField final { return (GetAccessFlags() & kAccFinal) != 0; } + bool IsPrivate() REQUIRES_SHARED(Locks::mutator_lock_) { + return (GetAccessFlags() & kAccPrivate) != 0; + } + uint32_t GetDexFieldIndex() { return field_dex_idx_; } diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 3aec1c3211..dfadc622d1 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -39,7 +39,6 @@ #include "mirror/object-inl.h" #include "mirror/object_array.h" #include "mirror/string.h" -#include "oat.h" #include "obj_ptr-inl.h" #include "quick/quick_method_frame_info.h" #include "read_barrier-inl.h" diff --git a/runtime/art_method.h b/runtime/art_method.h index d4fb5d7b90..7b435d5893 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -110,14 +110,14 @@ class ArtMethod final { return MemberOffset(OFFSETOF_MEMBER(ArtMethod, declaring_class_)); } - uint32_t GetAccessFlags() { + uint32_t GetAccessFlags() const { return access_flags_.load(std::memory_order_relaxed); } // This version should only be called when it's certain there is no // concurrency so there is no need to guarantee atomicity. For example, // before the method is linked. - void SetAccessFlags(uint32_t new_access_flags) { + void SetAccessFlags(uint32_t new_access_flags) REQUIRES_SHARED(Locks::mutator_lock_) { access_flags_.store(new_access_flags, std::memory_order_relaxed); } @@ -129,32 +129,32 @@ class ArtMethod final { InvokeType GetInvokeType() REQUIRES_SHARED(Locks::mutator_lock_); // Returns true if the method is declared public. - bool IsPublic() { + bool IsPublic() const { return (GetAccessFlags() & kAccPublic) != 0; } // Returns true if the method is declared private. - bool IsPrivate() { + bool IsPrivate() const { return (GetAccessFlags() & kAccPrivate) != 0; } // Returns true if the method is declared static. - bool IsStatic() { + bool IsStatic() const { return (GetAccessFlags() & kAccStatic) != 0; } // Returns true if the method is a constructor according to access flags. - bool IsConstructor() { + bool IsConstructor() const { return (GetAccessFlags() & kAccConstructor) != 0; } // Returns true if the method is a class initializer according to access flags. - bool IsClassInitializer() { + bool IsClassInitializer() const { return IsConstructor() && IsStatic(); } // Returns true if the method is static, private, or a constructor. - bool IsDirect() { + bool IsDirect() const { return IsDirect(GetAccessFlags()); } @@ -164,22 +164,22 @@ class ArtMethod final { } // Returns true if the method is declared synchronized. - bool IsSynchronized() { + bool IsSynchronized() const { constexpr uint32_t synchonized = kAccSynchronized | kAccDeclaredSynchronized; return (GetAccessFlags() & synchonized) != 0; } - bool IsFinal() { + bool IsFinal() const { return (GetAccessFlags() & kAccFinal) != 0; } - bool IsIntrinsic() { + bool IsIntrinsic() const { return (GetAccessFlags() & kAccIntrinsic) != 0; } ALWAYS_INLINE void SetIntrinsic(uint32_t intrinsic) REQUIRES_SHARED(Locks::mutator_lock_); - uint32_t GetIntrinsic() { + uint32_t GetIntrinsic() const { static const int kAccFlagsShift = CTZ(kAccIntrinsicBits); static_assert(IsPowerOfTwo((kAccIntrinsicBits >> kAccFlagsShift) + 1), "kAccIntrinsicBits are not continuous"); @@ -191,7 +191,7 @@ class ArtMethod final { void SetNotIntrinsic() REQUIRES_SHARED(Locks::mutator_lock_); - bool IsCopied() { + bool IsCopied() const { static_assert((kAccCopied & (kAccIntrinsic | kAccIntrinsicBits)) == 0, "kAccCopied conflicts with intrinsic modifier"); const bool copied = (GetAccessFlags() & kAccCopied) != 0; @@ -201,7 +201,7 @@ class ArtMethod final { return copied; } - bool IsMiranda() { + bool IsMiranda() const { // The kAccMiranda flag value is used with a different meaning for native methods and methods // marked kAccCompileDontBother, so we need to check these flags as well. return (GetAccessFlags() & (kAccNative | kAccMiranda | kAccCompileDontBother)) == kAccMiranda; @@ -209,26 +209,26 @@ class ArtMethod final { // Returns true if invoking this method will not throw an AbstractMethodError or // IncompatibleClassChangeError. - bool IsInvokable() { + bool IsInvokable() const { return !IsAbstract() && !IsDefaultConflicting(); } - bool IsPreCompiled() { + bool IsPreCompiled() const { uint32_t expected = (kAccPreCompiled | kAccCompileDontBother); return (GetAccessFlags() & expected) == expected; } - void SetPreCompiled() { + void SetPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(IsInvokable()); DCHECK(IsCompilable()); AddAccessFlags(kAccPreCompiled | kAccCompileDontBother); } - void ClearPreCompiled() { + void ClearPreCompiled() REQUIRES_SHARED(Locks::mutator_lock_) { ClearAccessFlags(kAccPreCompiled | kAccCompileDontBother); } - bool IsCompilable() { + bool IsCompilable() const { if (IsIntrinsic()) { // kAccCompileDontBother overlaps with kAccIntrinsicBits. return true; @@ -239,7 +239,12 @@ class ArtMethod final { return (GetAccessFlags() & kAccCompileDontBother) == 0; } - void SetDontCompile() { + void ClearDontCompile() REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(!IsMiranda()); + ClearAccessFlags(kAccCompileDontBother); + } + + void SetDontCompile() REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsMiranda()); AddAccessFlags(kAccCompileDontBother); } @@ -247,7 +252,7 @@ class ArtMethod final { // A default conflict method is a special sentinel method that stands for a conflict between // multiple default methods. It cannot be invoked, throwing an IncompatibleClassChangeError if one // attempts to do so. - bool IsDefaultConflicting() { + bool IsDefaultConflicting() const { if (IsIntrinsic()) { return false; } @@ -255,26 +260,26 @@ class ArtMethod final { } // This is set by the class linker. - bool IsDefault() { + bool IsDefault() const { static_assert((kAccDefault & (kAccIntrinsic | kAccIntrinsicBits)) == 0, "kAccDefault conflicts with intrinsic modifier"); return (GetAccessFlags() & kAccDefault) != 0; } - bool IsObsolete() { + bool IsObsolete() const { return (GetAccessFlags() & kAccObsoleteMethod) != 0; } - void SetIsObsolete() { + void SetIsObsolete() REQUIRES_SHARED(Locks::mutator_lock_) { AddAccessFlags(kAccObsoleteMethod); } - bool IsNative() { + bool IsNative() const { return (GetAccessFlags() & kAccNative) != 0; } // Checks to see if the method was annotated with @dalvik.annotation.optimization.FastNative. - bool IsFastNative() { + bool IsFastNative() const { // The presence of the annotation is checked by ClassLinker and recorded in access flags. // The kAccFastNative flag value is used with a different meaning for non-native methods, // so we need to check the kAccNative flag as well. @@ -283,7 +288,7 @@ class ArtMethod final { } // Checks to see if the method was annotated with @dalvik.annotation.optimization.CriticalNative. - bool IsCriticalNative() { + bool IsCriticalNative() const { // The presence of the annotation is checked by ClassLinker and recorded in access flags. // The kAccCriticalNative flag value is used with a different meaning for non-native methods, // so we need to check the kAccNative flag as well. @@ -291,15 +296,15 @@ class ArtMethod final { return (GetAccessFlags() & mask) == mask; } - bool IsAbstract() { + bool IsAbstract() const { return (GetAccessFlags() & kAccAbstract) != 0; } - bool IsSynthetic() { + bool IsSynthetic() const { return (GetAccessFlags() & kAccSynthetic) != 0; } - bool IsVarargs() { + bool IsVarargs() const { return (GetAccessFlags() & kAccVarargs) != 0; } @@ -307,41 +312,41 @@ class ArtMethod final { bool IsPolymorphicSignature() REQUIRES_SHARED(Locks::mutator_lock_); - bool UseFastInterpreterToInterpreterInvoke() { + bool UseFastInterpreterToInterpreterInvoke() const { // The bit is applicable only if the method is not intrinsic. constexpr uint32_t mask = kAccFastInterpreterToInterpreterInvoke | kAccIntrinsic; return (GetAccessFlags() & mask) == kAccFastInterpreterToInterpreterInvoke; } - void SetFastInterpreterToInterpreterInvokeFlag() { + void SetFastInterpreterToInterpreterInvokeFlag() REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsIntrinsic()); AddAccessFlags(kAccFastInterpreterToInterpreterInvoke); } - void ClearFastInterpreterToInterpreterInvokeFlag() { + void ClearFastInterpreterToInterpreterInvokeFlag() REQUIRES_SHARED(Locks::mutator_lock_) { if (!IsIntrinsic()) { ClearAccessFlags(kAccFastInterpreterToInterpreterInvoke); } } - bool SkipAccessChecks() { + bool SkipAccessChecks() const { // The kAccSkipAccessChecks flag value is used with a different meaning for native methods, // so we need to check the kAccNative flag as well. return (GetAccessFlags() & (kAccSkipAccessChecks | kAccNative)) == kAccSkipAccessChecks; } - void SetSkipAccessChecks() { + void SetSkipAccessChecks() REQUIRES_SHARED(Locks::mutator_lock_) { // SkipAccessChecks() is applicable only to non-native methods. DCHECK(!IsNative()); AddAccessFlags(kAccSkipAccessChecks); } - void ClearSkipAccessChecks() { + void ClearSkipAccessChecks() REQUIRES_SHARED(Locks::mutator_lock_) { // SkipAccessChecks() is applicable only to non-native methods. DCHECK(!IsNative()); ClearAccessFlags(kAccSkipAccessChecks); } - bool PreviouslyWarm() { + bool PreviouslyWarm() const { if (IsIntrinsic()) { // kAccPreviouslyWarm overlaps with kAccIntrinsicBits. return true; @@ -349,7 +354,7 @@ class ArtMethod final { return (GetAccessFlags() & kAccPreviouslyWarm) != 0; } - void SetPreviouslyWarm() { + void SetPreviouslyWarm() REQUIRES_SHARED(Locks::mutator_lock_) { if (IsIntrinsic()) { // kAccPreviouslyWarm overlaps with kAccIntrinsicBits. return; @@ -359,14 +364,18 @@ class ArtMethod final { // Should this method be run in the interpreter and count locks (e.g., failed structured- // locking verification)? - bool MustCountLocks() { + bool MustCountLocks() const { if (IsIntrinsic()) { return false; } return (GetAccessFlags() & kAccMustCountLocks) != 0; } - void SetMustCountLocks() { + void ClearMustCountLocks() REQUIRES_SHARED(Locks::mutator_lock_) { + ClearAccessFlags(kAccMustCountLocks); + } + + void SetMustCountLocks() REQUIRES_SHARED(Locks::mutator_lock_) { AddAccessFlags(kAccMustCountLocks); ClearAccessFlags(kAccSkipAccessChecks); } @@ -402,11 +411,11 @@ class ArtMethod final { return MemberOffset(OFFSETOF_MEMBER(ArtMethod, method_index_)); } - uint32_t GetCodeItemOffset() { + uint32_t GetCodeItemOffset() const { return dex_code_item_offset_; } - void SetCodeItemOffset(uint32_t new_code_off) { + void SetCodeItemOffset(uint32_t new_code_off) REQUIRES_SHARED(Locks::mutator_lock_) { // Not called within a transaction. dex_code_item_offset_ = new_code_off; } @@ -414,11 +423,11 @@ class ArtMethod final { // Number of 32bit registers that would be required to hold all the arguments static size_t NumArgRegisters(const char* shorty); - ALWAYS_INLINE uint32_t GetDexMethodIndex() { + ALWAYS_INLINE uint32_t GetDexMethodIndex() const { return dex_method_index_; } - void SetDexMethodIndex(uint32_t new_idx) { + void SetDexMethodIndex(uint32_t new_idx) REQUIRES_SHARED(Locks::mutator_lock_) { // Not called within a transaction. dex_method_index_ = new_idx; } @@ -448,20 +457,23 @@ class ArtMethod final { void Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, const char* shorty) REQUIRES_SHARED(Locks::mutator_lock_); - const void* GetEntryPointFromQuickCompiledCode() { + const void* GetEntryPointFromQuickCompiledCode() const { return GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize); } - ALWAYS_INLINE const void* GetEntryPointFromQuickCompiledCodePtrSize(PointerSize pointer_size) { + ALWAYS_INLINE + const void* GetEntryPointFromQuickCompiledCodePtrSize(PointerSize pointer_size) const { return GetNativePointer<const void*>( EntryPointFromQuickCompiledCodeOffset(pointer_size), pointer_size); } - void SetEntryPointFromQuickCompiledCode(const void* entry_point_from_quick_compiled_code) { + void SetEntryPointFromQuickCompiledCode(const void* entry_point_from_quick_compiled_code) + REQUIRES_SHARED(Locks::mutator_lock_) { SetEntryPointFromQuickCompiledCodePtrSize(entry_point_from_quick_compiled_code, kRuntimePointerSize); } ALWAYS_INLINE void SetEntryPointFromQuickCompiledCodePtrSize( - const void* entry_point_from_quick_compiled_code, PointerSize pointer_size) { + const void* entry_point_from_quick_compiled_code, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { SetNativePointer(EntryPointFromQuickCompiledCodeOffset(pointer_size), entry_point_from_quick_compiled_code, pointer_size); @@ -491,12 +503,13 @@ class ArtMethod final { * static_cast<size_t>(pointer_size)); } - ImtConflictTable* GetImtConflictTable(PointerSize pointer_size) { + ImtConflictTable* GetImtConflictTable(PointerSize pointer_size) const { DCHECK(IsRuntimeMethod()); return reinterpret_cast<ImtConflictTable*>(GetDataPtrSize(pointer_size)); } - ALWAYS_INLINE void SetImtConflictTable(ImtConflictTable* table, PointerSize pointer_size) { + ALWAYS_INLINE void SetImtConflictTable(ImtConflictTable* table, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(IsRuntimeMethod()); SetDataPtrSize(table, pointer_size); } @@ -508,11 +521,12 @@ class ArtMethod final { return reinterpret_cast<ProfilingInfo*>(GetDataPtrSize(pointer_size)); } - ALWAYS_INLINE void SetProfilingInfo(ProfilingInfo* info) { + ALWAYS_INLINE void SetProfilingInfo(ProfilingInfo* info) REQUIRES_SHARED(Locks::mutator_lock_) { SetDataPtrSize(info, kRuntimePointerSize); } - ALWAYS_INLINE void SetProfilingInfoPtrSize(ProfilingInfo* info, PointerSize pointer_size) { + ALWAYS_INLINE void SetProfilingInfoPtrSize(ProfilingInfo* info, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { SetDataPtrSize(info, pointer_size); } @@ -524,7 +538,8 @@ class ArtMethod final { template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> ALWAYS_INLINE bool HasSingleImplementation() REQUIRES_SHARED(Locks::mutator_lock_); - ALWAYS_INLINE void SetHasSingleImplementation(bool single_impl) { + ALWAYS_INLINE void SetHasSingleImplementation(bool single_impl) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsIntrinsic()) << "conflict with intrinsic bits"; if (single_impl) { AddAccessFlags(kAccSingleImplementation); @@ -533,6 +548,10 @@ class ArtMethod final { } } + ALWAYS_INLINE bool HasSingleImplementationFlag() const { + return (GetAccessFlags() & kAccSingleImplementation) != 0; + } + // Takes a method and returns a 'canonical' one if the method is default (and therefore // potentially copied from some other class). For example, this ensures that the debugger does not // get confused as to which method we are in. @@ -541,44 +560,48 @@ class ArtMethod final { ArtMethod* GetSingleImplementation(PointerSize pointer_size); - ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) { + ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsNative()); // Non-abstract method's single implementation is just itself. DCHECK(IsAbstract()); SetDataPtrSize(method, pointer_size); } - void* GetEntryPointFromJni() { + void* GetEntryPointFromJni() const { DCHECK(IsNative()); return GetEntryPointFromJniPtrSize(kRuntimePointerSize); } - ALWAYS_INLINE void* GetEntryPointFromJniPtrSize(PointerSize pointer_size) { + ALWAYS_INLINE void* GetEntryPointFromJniPtrSize(PointerSize pointer_size) const { return GetDataPtrSize(pointer_size); } - void SetEntryPointFromJni(const void* entrypoint) { + void SetEntryPointFromJni(const void* entrypoint) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(IsNative()); SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize); } - ALWAYS_INLINE void SetEntryPointFromJniPtrSize(const void* entrypoint, PointerSize pointer_size) { + ALWAYS_INLINE void SetEntryPointFromJniPtrSize(const void* entrypoint, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { SetDataPtrSize(entrypoint, pointer_size); } - ALWAYS_INLINE void* GetDataPtrSize(PointerSize pointer_size) { + ALWAYS_INLINE void* GetDataPtrSize(PointerSize pointer_size) const { DCHECK(IsImagePointerSize(pointer_size)); return GetNativePointer<void*>(DataOffset(pointer_size), pointer_size); } - ALWAYS_INLINE void SetDataPtrSize(const void* data, PointerSize pointer_size) { + ALWAYS_INLINE void SetDataPtrSize(const void* data, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(IsImagePointerSize(pointer_size)); SetNativePointer(DataOffset(pointer_size), data, pointer_size); } // Is this a CalleSaveMethod or ResolutionMethod and therefore doesn't adhere to normal // conventions for a method of managed code. Returns false for Proxy methods. - ALWAYS_INLINE bool IsRuntimeMethod() { + ALWAYS_INLINE bool IsRuntimeMethod() const { return dex_method_index_ == kRuntimeMethodDexMethodIndex; } @@ -726,13 +749,14 @@ class ArtMethod final { // Update entry points by passing them through the visitor. template <typename Visitor> - ALWAYS_INLINE void UpdateEntrypoints(const Visitor& visitor, PointerSize pointer_size); + ALWAYS_INLINE void UpdateEntrypoints(const Visitor& visitor, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_); // Visit the individual members of an ArtMethod. Used by imgdiag. // As imgdiag does not support mixing instruction sets or pointer sizes (e.g., using imgdiag32 // to inspect 64-bit images, etc.), we can go beneath the accessors directly to the class members. template <typename VisitorFunc> - void VisitMembers(VisitorFunc& visitor) { + void VisitMembers(VisitorFunc& visitor) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(IsImagePointerSize(kRuntimePointerSize)); visitor(this, &declaring_class_, "declaring_class_"); visitor(this, &access_flags_, "access_flags_"); @@ -843,7 +867,8 @@ class ArtMethod final { } template<typename T> - ALWAYS_INLINE void SetNativePointer(MemberOffset offset, T new_value, PointerSize pointer_size) { + ALWAYS_INLINE void SetNativePointer(MemberOffset offset, T new_value, PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_) { static_assert(std::is_pointer<T>::value, "T must be a pointer type"); const auto addr = reinterpret_cast<uintptr_t>(this) + offset.Uint32Value(); if (pointer_size == PointerSize::k32) { @@ -864,7 +889,7 @@ class ArtMethod final { } // This setter guarantees atomicity. - void AddAccessFlags(uint32_t flag) { + void AddAccessFlags(uint32_t flag) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsIntrinsic() || !OverlapsIntrinsicBits(flag) || IsValidIntrinsicUpdate(flag)); @@ -877,7 +902,7 @@ class ArtMethod final { } // This setter guarantees atomicity. - void ClearAccessFlags(uint32_t flag) { + void ClearAccessFlags(uint32_t flag) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!IsIntrinsic() || !OverlapsIntrinsicBits(flag) || IsValidIntrinsicUpdate(flag)); uint32_t old_access_flags; uint32_t new_access_flags; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 9c61386f4e..c87d0f7fd3 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -3759,7 +3759,8 @@ void ClassLinker::FixupStaticTrampolines(ObjPtr<mirror::Class> klass) { // Does anything needed to make sure that the compiler will not generate a direct invoke to this // method. Should only be called on non-invokable methods. -inline void EnsureThrowsInvocationError(ClassLinker* class_linker, ArtMethod* method) { +inline void EnsureThrowsInvocationError(ClassLinker* class_linker, ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(method != nullptr); DCHECK(!method->IsInvokable()); method->SetEntryPointFromQuickCompiledCodePtrSize( diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index df7aee8446..4f438e7aec 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -30,6 +30,7 @@ #include "dexopt_test.h" #include "gc/space/image_space.h" #include "hidden_api.h" +#include "oat.h" namespace art { void DexoptTest::SetUp() { @@ -116,24 +117,24 @@ void DexoptTest::GenerateOatForTest(const std::string& dex_location, ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; EXPECT_EQ(filter, odex_file->GetCompilerFilter()); - std::string boot_image_checksums = gc::space::ImageSpace::GetBootClassPathChecksums( - ArrayRef<const std::string>(Runtime::Current()->GetBootClassPath()), - image_location, - kRuntimeISA, - gc::space::ImageSpaceLoadingOrder::kSystemFirst, - &error_msg); - ASSERT_FALSE(boot_image_checksums.empty()) << error_msg; - - const OatHeader& oat_header = odex_file->GetOatHeader(); - if (CompilerFilter::DependsOnImageChecksum(filter)) { + const OatHeader& oat_header = odex_file->GetOatHeader(); + const char* oat_bcp = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey); + ASSERT_TRUE(oat_bcp != nullptr); + ASSERT_EQ(oat_bcp, android::base::Join(Runtime::Current()->GetBootClassPathLocations(), ':')); const char* checksums = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); ASSERT_TRUE(checksums != nullptr); - if (with_alternate_image) { - EXPECT_NE(boot_image_checksums, checksums); - } else { - EXPECT_EQ(boot_image_checksums, checksums); - } + + bool match = gc::space::ImageSpace::VerifyBootClassPathChecksums( + checksums, + oat_bcp, + image_location, + ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()), + ArrayRef<const std::string>(Runtime::Current()->GetBootClassPath()), + kRuntimeISA, + gc::space::ImageSpaceLoadingOrder::kSystemFirst, + &error_msg); + ASSERT_EQ(!with_alternate_image, match) << error_msg; } } diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 34a165f5a9..6a52d24f38 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -48,6 +48,7 @@ #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" #include "mirror/var_handle.h" +#include "oat.h" #include "oat_file.h" #include "oat_quick_method_header.h" #include "quick_exception_handler.h" @@ -2727,6 +2728,10 @@ extern "C" TwoWordReturn artInvokeInterfaceTrampoline(ArtMethod* interface_metho // We arrive here if we have found an implementation, and it is not in the ImtConflictTable. // We create a new table with the new pair { interface_method, method }. DCHECK(conflict_method->IsRuntimeMethod()); + + // Classes in the boot image should never need to update conflict methods in + // their IMT. + CHECK(!Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(cls.Get())); ArtMethod* new_conflict_method = Runtime::Current()->GetClassLinker()->AddMethodToConflictTable( cls.Get(), conflict_method, diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index a1663c8503..53a03fd007 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -2023,12 +2023,12 @@ void ConcurrentCopying::RevokeThreadLocalMarkStacks(bool disable_weak_ref_access } void ConcurrentCopying::RevokeThreadLocalMarkStack(Thread* thread) { - Thread* self = Thread::Current(); - CHECK_EQ(self, thread); accounting::AtomicStack<mirror::Object>* tl_mark_stack = thread->GetThreadLocalMarkStack(); if (tl_mark_stack != nullptr) { - CHECK(is_marking_); - MutexLock mu(self, mark_stack_lock_); + // With 2-phase CC change, we cannot assert that is_marking_ will always be true + // as we perform thread stack scan even before enabling the read-barrier. + CHECK(is_marking_ || (use_generational_cc_ && !young_gen_)); + MutexLock mu(Thread::Current(), mark_stack_lock_); revoked_mark_stacks_.push_back(tl_mark_stack); RemoveThreadMarkStackMapping(thread, tl_mark_stack); thread->SetThreadLocalMarkStack(nullptr); diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc index 88f5d4e0d0..b3a14e26d5 100644 --- a/runtime/gc/collector/immune_spaces_test.cc +++ b/runtime/gc/collector/immune_spaces_test.cc @@ -126,6 +126,8 @@ class ImmuneSpacesTest : public CommonRuntimeTest { /*oat_file_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size), /*boot_image_begin=*/ 0u, /*boot_image_size=*/ 0u, + /*boot_image_component_count=*/ 0u, + /*boot_image_checksum=*/ 0u, /*pointer_size=*/ sizeof(void*)); return new DummyImageSpace(std::move(image_map), std::move(live_bitmap), diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h index 08cbea2172..c1b3a63307 100644 --- a/runtime/gc/heap-inl.h +++ b/runtime/gc/heap-inl.h @@ -47,6 +47,12 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, size_t byte_count, AllocatorType allocator, const PreFenceVisitor& pre_fence_visitor) { + auto no_suspend_pre_fence_visitor = + [&pre_fence_visitor](auto... x) REQUIRES_SHARED(Locks::mutator_lock_) { + ScopedAssertNoThreadSuspension sants("No thread suspension during pre-fence visitor"); + pre_fence_visitor(x...); + }; + if (kIsDebugBuild) { CheckPreconditionsForAllocObject(klass, byte_count); // Since allocation can cause a GC which will need to SuspendAll, make sure all allocations are @@ -92,7 +98,7 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, } bytes_allocated = byte_count; usable_size = bytes_allocated; - pre_fence_visitor(obj, usable_size); + no_suspend_pre_fence_visitor(obj, usable_size); QuasiAtomic::ThreadFenceForConstructor(); } else if ( !kInstrumented && allocator == kAllocatorTypeRosAlloc && @@ -104,7 +110,7 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, obj->AssertReadBarrierState(); } usable_size = bytes_allocated; - pre_fence_visitor(obj, usable_size); + no_suspend_pre_fence_visitor(obj, usable_size); QuasiAtomic::ThreadFenceForConstructor(); } else { // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct @@ -148,7 +154,7 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, // case the object is non movable and points to a recently allocated movable class. WriteBarrier::ForFieldWrite(obj, mirror::Object::ClassOffset(), klass); } - pre_fence_visitor(obj, usable_size); + no_suspend_pre_fence_visitor(obj, usable_size); QuasiAtomic::ThreadFenceForConstructor(); if (bytes_tl_bulk_allocated > 0) { size_t num_bytes_allocated_before = diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 4b63138dd6..85b79da329 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -1205,8 +1205,8 @@ void Heap::ResetGcPerformanceInfo() { post_gc_last_process_cpu_time_ns_ = process_cpu_start_time_ns_; post_gc_weighted_allocated_bytes_ = 0u; - total_bytes_freed_ever_ = 0; - total_objects_freed_ever_ = 0; + total_bytes_freed_ever_.store(0); + total_objects_freed_ever_.store(0); total_wait_time_ = 0; blocking_gc_count_ = 0; blocking_gc_time_ = 0; @@ -1903,7 +1903,21 @@ uint64_t Heap::GetObjectsAllocatedEver() const { } uint64_t Heap::GetBytesAllocatedEver() const { - return GetBytesFreedEver() + GetBytesAllocated(); + // Force the returned value to be monotonically increasing, in the sense that if this is called + // at A and B, such that A happens-before B, then the call at B returns a value no smaller than + // that at A. This is not otherwise guaranteed, since num_bytes_allocated_ is decremented first, + // and total_bytes_freed_ever_ is incremented later. + static std::atomic<uint64_t> max_bytes_so_far(0); + uint64_t so_far = max_bytes_so_far.load(std::memory_order_relaxed); + uint64_t current_bytes = GetBytesFreedEver(std::memory_order_acquire); + current_bytes += GetBytesAllocated(); + do { + if (current_bytes <= so_far) { + return so_far; + } + } while (!max_bytes_so_far.compare_exchange_weak(so_far /* updated */, + current_bytes, std::memory_order_relaxed)); + return current_bytes; } // Check whether the given object is an instance of the given class. @@ -2239,6 +2253,19 @@ void Heap::UnBindBitmaps() { } } +void Heap::IncrementFreedEver() { + // Counters are updated only by us, but may be read concurrently. + // The updates should become visible after the corresponding live object info. + total_objects_freed_ever_.store(total_objects_freed_ever_.load(std::memory_order_relaxed) + + GetCurrentGcIteration()->GetFreedObjects() + + GetCurrentGcIteration()->GetFreedLargeObjects(), + std::memory_order_release); + total_bytes_freed_ever_.store(total_bytes_freed_ever_.load(std::memory_order_relaxed) + + GetCurrentGcIteration()->GetFreedBytes() + + GetCurrentGcIteration()->GetFreedLargeObjectBytes(), + std::memory_order_release); +} + void Heap::PreZygoteFork() { if (!HasZygoteSpace()) { // We still want to GC in case there is some unreachable non moving objects that could cause a @@ -2313,10 +2340,7 @@ void Heap::PreZygoteFork() { if (temp_space_ != nullptr) { CHECK(temp_space_->IsEmpty()); } - total_objects_freed_ever_ += GetCurrentGcIteration()->GetFreedObjects() + - GetCurrentGcIteration()->GetFreedLargeObjects(); - total_bytes_freed_ever_ += GetCurrentGcIteration()->GetFreedBytes() + - GetCurrentGcIteration()->GetFreedLargeObjectBytes(); + IncrementFreedEver(); // Update the end and write out image. non_moving_space_->SetEnd(target_space.End()); non_moving_space_->SetLimit(target_space.Limit()); @@ -2588,10 +2612,7 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, << "Could not find garbage collector with collector_type=" << static_cast<size_t>(collector_type_) << " and gc_type=" << gc_type; collector->Run(gc_cause, clear_soft_references || runtime->IsZygote()); - total_objects_freed_ever_ += GetCurrentGcIteration()->GetFreedObjects() + - GetCurrentGcIteration()->GetFreedLargeObjects(); - total_bytes_freed_ever_ += GetCurrentGcIteration()->GetFreedBytes() + - GetCurrentGcIteration()->GetFreedLargeObjectBytes(); + IncrementFreedEver(); RequestTrim(self); // Collect cleared references. SelfDeletingTask* clear = reference_processor_->CollectClearedReferences(self); diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h index 6c3290f8ee..9ef6af5c97 100644 --- a/runtime/gc/heap.h +++ b/runtime/gc/heap.h @@ -553,13 +553,15 @@ class Heap { uint64_t GetBytesAllocatedEver() const; // Returns the total number of objects freed since the heap was created. - uint64_t GetObjectsFreedEver() const { - return total_objects_freed_ever_; + // With default memory order, this should be viewed only as a hint. + uint64_t GetObjectsFreedEver(std::memory_order mo = std::memory_order_relaxed) const { + return total_objects_freed_ever_.load(mo); } // Returns the total number of bytes freed since the heap was created. - uint64_t GetBytesFreedEver() const { - return total_bytes_freed_ever_; + // With default memory order, this should be viewed only as a hint. + uint64_t GetBytesFreedEver(std::memory_order mo = std::memory_order_relaxed) const { + return total_bytes_freed_ever_.load(mo); } space::RegionSpace* GetRegionSpace() const { @@ -1189,6 +1191,9 @@ class Heap { ALWAYS_INLINE void IncrementNumberOfBytesFreedRevoke(size_t freed_bytes_revoke); + // Update *_freed_ever_ counters to reflect current GC values. + void IncrementFreedEver(); + // Remove a vlog code from heap-inl.h which is transitively included in half the world. static void VlogHeapGrowth(size_t max_allowed_footprint, size_t new_footprint, size_t alloc_size); @@ -1342,10 +1347,10 @@ class Heap { size_t concurrent_start_bytes_; // Since the heap was created, how many bytes have been freed. - uint64_t total_bytes_freed_ever_; + std::atomic<uint64_t> total_bytes_freed_ever_; // Since the heap was created, how many objects have been freed. - uint64_t total_objects_freed_ever_; + std::atomic<uint64_t> total_objects_freed_ever_; // Number of bytes currently allocated and not yet reclaimed. Includes active // TLABS in their entirety, even if they have not yet been parceled out. diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 63fc90c2dd..9ff799c650 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -37,6 +37,7 @@ #include "base/os.h" #include "base/scoped_flock.h" #include "base/stl_util.h" +#include "base/string_view_cpp20.h" #include "base/systrace.h" #include "base/time_utils.h" #include "base/utils.h" @@ -53,6 +54,7 @@ #include "mirror/executable-inl.h" #include "mirror/object-inl.h" #include "mirror/object-refvisitor-inl.h" +#include "oat.h" #include "oat_file.h" #include "runtime.h" #include "space-inl.h" @@ -64,6 +66,10 @@ namespace space { using android::base::StringAppendF; using android::base::StringPrintf; +// We do not allow the boot image and extensions to take more than 1GiB. They are +// supposed to be much smaller and allocating more that this would likely fail anyway. +static constexpr size_t kMaxTotalImageReservationSize = 1 * GB; + Atomic<uint32_t> ImageSpace::bitmap_index_(0); ImageSpace::ImageSpace(const std::string& image_filename, @@ -813,6 +819,10 @@ class ImageSpace::Loader { image_filename); return nullptr; } + if (!ValidateBootImageChecksum(image_filename, *image_header, oat_file, error_msg)) { + DCHECK(!error_msg->empty()); + return nullptr; + } } if (VLOG_IS_ON(startup)) { @@ -936,6 +946,72 @@ class ImageSpace::Loader { } private: + static bool ValidateBootImageChecksum(const char* image_filename, + const ImageHeader& image_header, + const OatFile* oat_file, + /*out*/std::string* error_msg) { + // Use the boot image component count to calculate the checksum from + // the appropriate number of boot image chunks. + const std::vector<ImageSpace*>& image_spaces = + Runtime::Current()->GetHeap()->GetBootImageSpaces(); + uint32_t boot_image_component_count = image_header.GetBootImageComponentCount(); + size_t image_spaces_size = image_spaces.size(); + if (boot_image_component_count > image_spaces_size) { + *error_msg = StringPrintf("Too many boot image dependencies (%u > %zu) in image %s", + boot_image_component_count, + image_spaces_size, + image_filename); + return false; + } + uint32_t checksum = 0u; + size_t chunk_count = 0u; + for (size_t component_count = 0u; component_count != boot_image_component_count; ) { + const ImageHeader& current_header = image_spaces[component_count]->GetImageHeader(); + if (current_header.GetComponentCount() > boot_image_component_count - component_count) { + *error_msg = StringPrintf("Boot image component count in %s ends in the middle of a chunk, " + "%u is between %zu and %zu", + image_filename, + boot_image_component_count, + component_count, + component_count + current_header.GetComponentCount()); + return false; + } + component_count += current_header.GetComponentCount(); + checksum ^= current_header.GetImageChecksum(); + chunk_count += 1u; + } + if (image_header.GetBootImageChecksum() != checksum) { + *error_msg = StringPrintf("Boot image checksum mismatch (0x%x != 0x%x) in image %s", + image_header.GetBootImageChecksum(), + checksum, + image_filename); + return false; + } + // Oat checksums, if present, have already been validated, so we know that + // they match the loaded image spaces. Therefore, we just verify that they + // are consistent in the number of boot image chunks they list by looking + // for the kImageChecksumPrefix at the start of each component. + const char* oat_boot_class_path_checksums = + oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); + if (oat_boot_class_path_checksums != nullptr) { + size_t oat_bcp_chunk_count = 0u; + while (*oat_boot_class_path_checksums == kImageChecksumPrefix) { + oat_bcp_chunk_count += 1u; + // Find the start of the next component if any. + const char* separator = strchr(oat_boot_class_path_checksums, ':'); + oat_boot_class_path_checksums = (separator != nullptr) ? separator + 1u : ""; + } + if (oat_bcp_chunk_count != chunk_count) { + *error_msg = StringPrintf("Boot image chunk count mismatch (%zu != %zu) in image %s", + oat_bcp_chunk_count, + chunk_count, + image_filename); + return false; + } + } + return true; + } + static MemMap LoadImageFile(const char* image_filename, const char* image_location, const ImageHeader& image_header, @@ -1036,9 +1112,9 @@ class ImageSpace::Loader { template <typename Range0, typename Range1 = EmptyRange, typename Range2 = EmptyRange> class ForwardAddress { public: - ForwardAddress(const Range0& range0 = Range0(), - const Range1& range1 = Range1(), - const Range2& range2 = Range2()) + explicit ForwardAddress(const Range0& range0 = Range0(), + const Range1& range1 = Range1(), + const Range2& range2 = Range2()) : range0_(range0), range1_(range1), range2_(range2) {} // Return the relocated address of a heap object. @@ -1370,6 +1446,592 @@ class ImageSpace::Loader { } }; +static void AppendImageChecksum(uint32_t component_count, + uint32_t checksum, + /*inout*/std::string* checksums) { + static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check."); + StringAppendF(checksums, "i;%u/%08x", component_count, checksum); +} + +static bool CheckAndRemoveImageChecksum(uint32_t component_count, + uint32_t checksum, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + std::string image_checksum; + AppendImageChecksum(component_count, checksum, &image_checksum); + if (!StartsWith(*oat_checksums, image_checksum)) { + *error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s", + std::string(*oat_checksums).c_str(), + image_checksum.c_str()); + return false; + } + oat_checksums->remove_prefix(image_checksum.size()); + return true; +} + +// Helper class to find the primary boot image and boot image extensions +// and determine the boot image layout. +class ImageSpace::BootImageLayout { + public: + // Description of a "chunk" of the boot image, i.e. either primary boot image + // or a boot image extension, used in conjunction with the boot class path to + // load boot image components. + struct ImageChunk { + std::string base_location; + std::string base_filename; + size_t start_index; + size_t component_count; + size_t reservation_size; + uint32_t checksum; + uint32_t boot_image_component_count; + uint32_t boot_image_checksum; + }; + + BootImageLayout(const std::string& image_location, ArrayRef<const std::string> boot_class_path) + : image_location_(image_location), + boot_class_path_(boot_class_path) {} + + std::string GetPrimaryImageLocation(); + + bool LoadFromSystem(InstructionSet image_isa, /*out*/std::string* error_msg) { + return LoadOrValidateFromSystem(image_isa, /*oat_checksums=*/ nullptr, error_msg); + } + + bool ValidateFromSystem(InstructionSet image_isa, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + DCHECK(oat_checksums != nullptr); + return LoadOrValidateFromSystem(image_isa, oat_checksums, error_msg); + } + + bool LoadFromDalvikCache(const std::string& dalvik_cache, /*out*/std::string* error_msg) { + return LoadOrValidateFromDalvikCache(dalvik_cache, /*oat_checksums=*/ nullptr, error_msg); + } + + bool ValidateFromDalvikCache(const std::string& dalvik_cache, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + DCHECK(oat_checksums != nullptr); + return LoadOrValidateFromDalvikCache(dalvik_cache, oat_checksums, error_msg); + } + + ArrayRef<const ImageChunk> GetChunks() const { + return ArrayRef<const ImageChunk>(chunks_); + } + + uint32_t GetBaseAddress() const { + return base_address_; + } + + size_t GetNextBcpIndex() const { + return next_bcp_index_; + } + + size_t GetTotalComponentCount() const { + return total_component_count_; + } + + size_t GetTotalReservationSize() const { + return total_reservation_size_; + } + + private: + std::string ExpandLocationImpl(const std::string& location, + size_t bcp_index, + bool boot_image_extension) { + std::vector<std::string> expanded = ExpandMultiImageLocations( + ArrayRef<const std::string>(boot_class_path_).SubArray(bcp_index, 1u), + location, + boot_image_extension); + DCHECK_EQ(expanded.size(), 1u); + return expanded[0]; + } + + std::string ExpandLocation(const std::string& location, size_t bcp_index) { + if (bcp_index == 0u) { + DCHECK_EQ(location, ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ false)); + return location; + } else { + return ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ true); + } + } + + bool VerifyImageLocation(const std::vector<std::string>& components, + /*out*/size_t* named_components_count, + /*out*/std::string* error_msg); + + bool MatchNamedComponents( + ArrayRef<const std::string> named_components, + /*out*/std::vector<std::pair<std::string, size_t>>* base_locations_and_bcp_indexes, + /*out*/std::string* error_msg); + + bool ValidateBootImageChecksum(const std::string& actual_filename, + const ImageHeader& header, + /*out*/std::string* error_msg); + + bool ReadHeader(const std::string& base_location, + const std::string& base_filename, + size_t bcp_index, + size_t bcp_component_count, + /*out*/std::string* error_msg); + + bool CheckAndRemoveLastChunkChecksum(/*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg); + + template <typename FilenameFn> + bool LoadOrValidate(FilenameFn&& filename_fn, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg); + + bool LoadOrValidateFromSystem(InstructionSet image_isa, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg); + + bool LoadOrValidateFromDalvikCache(const std::string& dalvik_cache, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg); + + const std::string& image_location_; + ArrayRef<const std::string> boot_class_path_; + + std::vector<ImageChunk> chunks_; + uint32_t base_address_ = 0u; + size_t next_bcp_index_ = 0u; + size_t total_component_count_ = 0u; + size_t total_reservation_size_ = 0u; +}; + +std::string ImageSpace::BootImageLayout::GetPrimaryImageLocation() { + size_t location_start = 0u; + size_t location_end = image_location_.find(':'); + while (location_end == location_start) { + ++location_start; + location_end = image_location_.find(location_start, ':'); + } + std::string location = (location_end == std::string::npos) + ? image_location_.substr(location_start) + : image_location_.substr(location_start, location_end - location_start); + if (image_location_.find('/') == std::string::npos) { + // No path, so use the path from the first boot class path component. + size_t slash_pos = boot_class_path_.empty() + ? std::string::npos + : boot_class_path_[0].rfind('/'); + if (slash_pos == std::string::npos) { + return std::string(); + } + location.insert(0u, boot_class_path_[0].substr(0u, slash_pos + 1u)); + } + return location; +} + +bool ImageSpace::BootImageLayout::VerifyImageLocation( + const std::vector<std::string>& components, + /*out*/size_t* named_components_count, + /*out*/std::string* error_msg) { + DCHECK(named_components_count != nullptr); + + // Validate boot class path. Require a path and non-empty name in each component. + for (const std::string& bcp_component : boot_class_path_) { + size_t bcp_slash_pos = bcp_component.rfind('/'); + if (bcp_slash_pos == std::string::npos || bcp_slash_pos == bcp_component.size() - 1u) { + *error_msg = StringPrintf("Invalid boot class path component: %s", bcp_component.c_str()); + return false; + } + } + + // Validate the format of image location components. + size_t components_size = components.size(); + if (components_size == 0u) { + *error_msg = "Empty image location."; + return false; + } + size_t wildcards_start = components_size; // No wildcards. + for (size_t i = 0; i != components_size; ++i) { + const std::string& component = components[i]; + DCHECK(!component.empty()); // Guaranteed by Split(). + size_t wildcard_pos = component.find('*'); + if (wildcard_pos == std::string::npos) { + if (wildcards_start != components.size()) { + *error_msg = + StringPrintf("Image component without wildcard after component with wildcard: %s", + component.c_str()); + return false; + } + if (component.back() == '/') { + *error_msg = StringPrintf("Image component ends with path separator: %s", + component.c_str()); + return false; + } + } else { + if (wildcards_start == components_size) { + wildcards_start = i; + } + // Wildcard must be the last character. + if (wildcard_pos != component.size() - 1u) { + *error_msg = StringPrintf("Unsupported wildcard (*) position in %s", component.c_str()); + return false; + } + // And it must be either plain wildcard or preceded by a path separator. + if (component.size() != 1u && component[wildcard_pos - 1u] != '/') { + *error_msg = StringPrintf("Non-plain wildcard (*) not preceded by path separator '/': %s", + component.c_str()); + return false; + } + if (i == 0) { + *error_msg = StringPrintf("Primary component contains wildcard (*): %s", component.c_str()); + return false; + } + } + } + + *named_components_count = wildcards_start; + return true; +} + +bool ImageSpace::BootImageLayout::MatchNamedComponents( + ArrayRef<const std::string> named_components, + /*out*/std::vector<std::pair<std::string, size_t>>* base_locations_and_bcp_indexes, + /*out*/std::string* error_msg) { + DCHECK(!named_components.empty()); + DCHECK(base_locations_and_bcp_indexes->empty()); + base_locations_and_bcp_indexes->reserve(named_components.size()); + size_t bcp_component_count = boot_class_path_.size(); + size_t bcp_pos = 0; + std::string base_name; + for (size_t i = 0, size = named_components.size(); i != size; ++i) { + const std::string& component = named_components[i]; + size_t slash_pos = component.rfind('/'); + std::string base_location; + if (i == 0u) { + // The primary boot image name is taken as provided. It forms the base + // for expanding the extension filenames. + if (slash_pos != std::string::npos) { + base_name = component.substr(slash_pos + 1u); + base_location = component; + } else { + base_name = component; + size_t bcp_slash_pos = boot_class_path_[0u].rfind('/'); + DCHECK_NE(bcp_slash_pos, std::string::npos); + base_location = boot_class_path_[0u].substr(0u, bcp_slash_pos + 1u) + component; + } + } else { + std::string to_match; + if (slash_pos != std::string::npos) { + // If we have the full path, we just need to match the filename to the BCP component. + base_location = component.substr(0u, slash_pos + 1u) + base_name; + to_match = component; + } + while (true) { + if (slash_pos == std::string::npos) { + // If we do not have a full path, we need to update the path based on the BCP location. + size_t bcp_slash_pos = boot_class_path_[bcp_pos].rfind('/'); + DCHECK_NE(bcp_slash_pos, std::string::npos); + std::string path = boot_class_path_[bcp_pos].substr(0u, bcp_slash_pos + 1u); + to_match = path + component; + base_location = path + base_name; + } + if (ExpandLocation(base_location, bcp_pos) == to_match) { + break; + } + ++bcp_pos; + if (bcp_pos == bcp_component_count) { + *error_msg = StringPrintf("Image component %s does not match a boot class path component", + component.c_str()); + return false; + } + } + } + base_locations_and_bcp_indexes->emplace_back(base_location, bcp_pos); + ++bcp_pos; + } + return true; +} + +bool ImageSpace::BootImageLayout::ValidateBootImageChecksum(const std::string& actual_filename, + const ImageHeader& header, + /*out*/std::string* error_msg) { + uint32_t boot_image_component_count = header.GetBootImageComponentCount(); + if (chunks_.empty() != (boot_image_component_count == 0u)) { + *error_msg = StringPrintf("Unexpected boot image component count in %s: %u, %s", + actual_filename.c_str(), + header.GetImageReservationSize(), + chunks_.empty() ? "should be 0" : "should not be 0"); + return false; + } + uint32_t component_count = 0u; + uint32_t composite_checksum = 0u; + for (const ImageChunk& chunk : chunks_) { + if (component_count == boot_image_component_count) { + break; // Hit the component count. + } + if (chunk.start_index != component_count) { + break; // End of contiguous chunks, fail below; same as reaching end of `chunks_`. + } + if (chunk.component_count > boot_image_component_count - component_count) { + *error_msg = StringPrintf("Boot image component count in %s ends in the middle of a chunk, " + "%u is between %u and %zu", + actual_filename.c_str(), + boot_image_component_count, + component_count, + component_count + chunk.component_count); + return false; + } + component_count += chunk.component_count; + composite_checksum ^= chunk.checksum; + } + DCHECK_LE(component_count, boot_image_component_count); + if (component_count != boot_image_component_count) { + *error_msg = StringPrintf("Missing boot image components for checksum in %s: %u > %u", + actual_filename.c_str(), + boot_image_component_count, + component_count); + return false; + } + if (composite_checksum != header.GetBootImageChecksum()) { + *error_msg = StringPrintf("Boot image checksum mismatch in %s: 0x%08x != 0x%08x", + actual_filename.c_str(), + header.GetBootImageChecksum(), + composite_checksum); + return false; + } + return true; +} + +bool ImageSpace::BootImageLayout::ReadHeader(const std::string& base_location, + const std::string& base_filename, + size_t bcp_index, + size_t bcp_component_count, + /*out*/std::string* error_msg) { + DCHECK_LE(next_bcp_index_, bcp_index); + DCHECK_LT(bcp_index, bcp_component_count); + size_t allowed_component_count = bcp_component_count - bcp_index; + DCHECK_LE(total_reservation_size_, kMaxTotalImageReservationSize); + size_t allowed_reservation_size = kMaxTotalImageReservationSize - total_reservation_size_; + + std::string actual_filename = ExpandLocation(base_filename, bcp_index); + ImageHeader header; + if (!ReadSpecificImageHeader(actual_filename.c_str(), &header, error_msg)) { + return false; + } + if (header.GetComponentCount() == 0u || + header.GetComponentCount() > allowed_component_count) { + *error_msg = StringPrintf("Unexpected component count in %s, received %u, " + "expected non-zero and <= %zu", + actual_filename.c_str(), + header.GetComponentCount(), + allowed_component_count); + return false; + } + if (header.GetImageReservationSize() > allowed_reservation_size) { + *error_msg = StringPrintf("Reservation size too big in %s: %u > %zu", + actual_filename.c_str(), + header.GetImageReservationSize(), + allowed_reservation_size); + return false; + } + if (!ValidateBootImageChecksum(actual_filename, header, error_msg)) { + return false; + } + + if (chunks_.empty()) { + base_address_ = reinterpret_cast32<uint32_t>(header.GetImageBegin()); + } + ImageChunk chunk; + chunk.base_location = base_location; + chunk.base_filename = base_filename; + chunk.start_index = bcp_index; + chunk.component_count = header.GetComponentCount(); + chunk.reservation_size = header.GetImageReservationSize(); + chunk.checksum = header.GetImageChecksum(); + chunk.boot_image_component_count = header.GetBootImageComponentCount(); + chunk.boot_image_checksum = header.GetBootImageChecksum(); + chunks_.push_back(std::move(chunk)); + next_bcp_index_ = bcp_index + header.GetComponentCount(); + total_component_count_ += header.GetComponentCount(); + total_reservation_size_ += header.GetImageReservationSize(); + return true; +} + +bool ImageSpace::BootImageLayout::CheckAndRemoveLastChunkChecksum( + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + DCHECK(oat_checksums != nullptr); + DCHECK(!chunks_.empty()); + const ImageChunk& chunk = chunks_.back(); + size_t component_count = chunk.component_count; + size_t checksum = chunk.checksum; + if (!CheckAndRemoveImageChecksum(component_count, checksum, oat_checksums, error_msg)) { + DCHECK(!error_msg->empty()); + return false; + } + if (oat_checksums->empty()) { + if (next_bcp_index_ != boot_class_path_.size()) { + *error_msg = StringPrintf("Checksum too short, missing %zu components.", + boot_class_path_.size() - next_bcp_index_); + return false; + } + return true; + } + if (!StartsWith(*oat_checksums, ":")) { + *error_msg = StringPrintf("Missing ':' separator at start of %s", + std::string(*oat_checksums).c_str()); + return false; + } + oat_checksums->remove_prefix(1u); + if (oat_checksums->empty()) { + *error_msg = "Missing checksums after the ':' separator."; + return false; + } + return true; +} + +template <typename FilenameFn> +bool ImageSpace::BootImageLayout::LoadOrValidate(FilenameFn&& filename_fn, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + DCHECK(GetChunks().empty()); + DCHECK_EQ(GetBaseAddress(), 0u); + bool validate = (oat_checksums != nullptr); + static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check."); + DCHECK(!validate || StartsWith(*oat_checksums, "i")); + + std::vector<std::string> components; + Split(image_location_, ':', &components); + size_t named_components_count = 0u; + if (!VerifyImageLocation(components, &named_components_count, error_msg)) { + return false; + } + + ArrayRef<const std::string> named_components = + ArrayRef<const std::string>(components).SubArray(/*pos=*/ 0u, named_components_count); + + std::vector<std::pair<std::string, size_t>> base_locations_and_bcp_indexes; + if (!MatchNamedComponents(named_components, &base_locations_and_bcp_indexes, error_msg)) { + return false; + } + + // Load the image headers of named components. + DCHECK_EQ(base_locations_and_bcp_indexes.size(), named_components.size()); + const size_t bcp_component_count = boot_class_path_.size(); + size_t bcp_pos = 0u; + for (size_t i = 0, size = named_components.size(); i != size; ++i) { + const std::string& base_location = base_locations_and_bcp_indexes[i].first; + size_t bcp_index = base_locations_and_bcp_indexes[i].second; + if (bcp_index < bcp_pos) { + DCHECK_NE(i, 0u); + LOG(ERROR) << "Named image component already covered by previous image: " << base_location; + continue; + } + if (validate && bcp_index > bcp_pos) { + *error_msg = StringPrintf("End of contiguous boot class path images, remaining checksum: %s", + std::string(*oat_checksums).c_str()); + return false; + } + std::string local_error_msg; + std::string* err_msg = (i == 0 || validate) ? error_msg : &local_error_msg; + std::string base_filename; + if (!filename_fn(base_location, &base_filename, err_msg) || + !ReadHeader(base_location, base_filename, bcp_index, bcp_component_count, err_msg)) { + if (i == 0u || validate) { + return false; + } + VLOG(image) << "Error reading named image component header for " << base_location + << ", error: " << local_error_msg; + bcp_pos = bcp_index + 1u; // Skip at least this component. + DCHECK_GT(bcp_pos, GetNextBcpIndex()); + continue; + } + if (validate) { + if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) { + return false; + } + if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) { + return true; // Let the caller deal with the dex file checksums if any. + } + } + bcp_pos = GetNextBcpIndex(); + } + + // Look for remaining components if there are any wildcard specifications. + ArrayRef<const std::string> search_paths = + ArrayRef<const std::string>(components).SubArray(/*pos=*/ named_components_count); + if (!search_paths.empty()) { + const std::string& primary_base_location = base_locations_and_bcp_indexes[0].first; + size_t base_slash_pos = primary_base_location.rfind('/'); + DCHECK_NE(base_slash_pos, std::string::npos); + std::string base_name = primary_base_location.substr(base_slash_pos + 1u); + DCHECK(!base_name.empty()); + while (bcp_pos != bcp_component_count) { + const std::string& bcp_component = boot_class_path_[bcp_pos]; + bool found = false; + for (const std::string& path : search_paths) { + std::string base_location; + if (path.size() == 1u) { + DCHECK_EQ(path, "*"); + size_t slash_pos = bcp_component.rfind('/'); + DCHECK_NE(slash_pos, std::string::npos); + base_location = bcp_component.substr(0u, slash_pos + 1u) + base_name; + } else { + DCHECK(EndsWith(path, "/*")); + base_location = path.substr(0u, path.size() - 1u) + base_name; + } + std::string err_msg; // Ignored. + std::string base_filename; + if (filename_fn(base_location, &base_filename, &err_msg) && + ReadHeader(base_location, base_filename, bcp_pos, bcp_component_count, &err_msg)) { + VLOG(image) << "Found image extension for " << ExpandLocation(base_location, bcp_pos); + bcp_pos = GetNextBcpIndex(); + found = true; + if (validate) { + if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) { + return false; + } + if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) { + return true; // Let the caller deal with the dex file checksums if any. + } + } + break; + } + } + if (!found) { + if (validate) { + *error_msg = StringPrintf("Missing extension for %s, remaining checksum: %s", + bcp_component.c_str(), + std::string(*oat_checksums).c_str()); + return false; + } + ++bcp_pos; + } + } + } + + return true; +} + +bool ImageSpace::BootImageLayout::LoadOrValidateFromSystem(InstructionSet image_isa, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + auto filename_fn = [image_isa](const std::string& location, + /*out*/std::string* filename, + /*out*/std::string* err_msg ATTRIBUTE_UNUSED) { + *filename = GetSystemImageFilename(location.c_str(), image_isa); + return true; + }; + return LoadOrValidate(filename_fn, oat_checksums, error_msg); +} + +bool ImageSpace::BootImageLayout::LoadOrValidateFromDalvikCache( + const std::string& dalvik_cache, + /*inout*/std::string_view* oat_checksums, + /*out*/std::string* error_msg) { + auto filename_fn = [&dalvik_cache](const std::string& location, + /*out*/std::string* filename, + /*out*/std::string* err_msg) { + return GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(), filename, err_msg); + }; + return LoadOrValidate(filename_fn, oat_checksums, error_msg); +} + class ImageSpace::BootImageLoader { public: BootImageLoader(const std::vector<std::string>& boot_class_path, @@ -1397,8 +2059,10 @@ class ImageSpace::BootImageLoader { bool IsZygote() const { return is_zygote_; } void FindImageFiles() { + BootImageLayout layout(image_location_, boot_class_path_); + std::string image_location = layout.GetPrimaryImageLocation(); std::string system_filename; - bool found_image = FindImageFilenameImpl(image_location_.c_str(), + bool found_image = FindImageFilenameImpl(image_location.c_str(), image_isa_, &has_system_, &system_filename, @@ -1427,126 +2091,115 @@ class ImageSpace::BootImageLoader { bool LoadFromSystem(bool validate_oat_file, size_t extra_reservation_size, - /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, /*out*/MemMap* extra_reservation, - /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { - TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image)); - std::string filename = GetSystemImageFilename(image_location_.c_str(), image_isa_); - - if (!LoadFromFile(filename, - validate_oat_file, - extra_reservation_size, - &logger, - boot_image_spaces, - extra_reservation, - error_msg)) { - return false; - } - - if (VLOG_IS_ON(image)) { - LOG(INFO) << "ImageSpace::BootImageLoader::LoadFromSystem exiting " - << boot_image_spaces->front(); - logger.Dump(LOG_STREAM(INFO)); - } - return true; - } + /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_); bool LoadFromDalvikCache( bool validate_oat_file, size_t extra_reservation_size, - /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, /*out*/MemMap* extra_reservation, - /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { - TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image)); - DCHECK(DalvikCacheExists()); - - if (!LoadFromFile(cache_filename_, - validate_oat_file, - extra_reservation_size, - &logger, - boot_image_spaces, - extra_reservation, - error_msg)) { - return false; - } - - if (VLOG_IS_ON(image)) { - LOG(INFO) << "ImageSpace::BootImageLoader::LoadFromDalvikCache exiting " - << boot_image_spaces->front(); - logger.Dump(LOG_STREAM(INFO)); - } - return true; - } + /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_); private: - bool LoadFromFile( - const std::string& filename, + bool LoadImage( + const BootImageLayout& layout, bool validate_oat_file, size_t extra_reservation_size, TimingLogger* logger, - /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, /*out*/MemMap* extra_reservation, /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { - ImageHeader system_hdr; - if (!ReadSpecificImageHeader(filename.c_str(), &system_hdr, error_msg)) { + ArrayRef<const BootImageLayout::ImageChunk> chunks = layout.GetChunks(); + DCHECK(!chunks.empty()); + const uint32_t base_address = layout.GetBaseAddress(); + const size_t image_component_count = layout.GetTotalComponentCount(); + const size_t image_reservation_size = layout.GetTotalReservationSize(); + + DCHECK_LE(image_reservation_size, kMaxTotalImageReservationSize); + static_assert(kMaxTotalImageReservationSize < std::numeric_limits<uint32_t>::max()); + if (extra_reservation_size > std::numeric_limits<uint32_t>::max() - image_reservation_size) { + // Since the `image_reservation_size` is limited to kMaxTotalImageReservationSize, + // the `extra_reservation_size` would have to be really excessive to fail this check. + *error_msg = StringPrintf("Excessive extra reservation size: %zu", extra_reservation_size); return false; } - if (system_hdr.GetComponentCount() == 0u || - system_hdr.GetComponentCount() > boot_class_path_.size()) { - *error_msg = StringPrintf("Unexpected component count in %s, received %u, " - "expected non-zero and <= %zu", - filename.c_str(), - system_hdr.GetComponentCount(), - boot_class_path_.size()); - return false; - } - MemMap image_reservation; - MemMap local_extra_reservation; - if (!ReserveBootImageMemory(system_hdr.GetImageReservationSize(), - reinterpret_cast32<uint32_t>(system_hdr.GetImageBegin()), - extra_reservation_size, - &image_reservation, - &local_extra_reservation, - error_msg)) { + + // Reserve address space. If relocating, choose a random address for ALSR. + uint8_t* addr = reinterpret_cast<uint8_t*>( + relocate_ ? ART_BASE_ADDRESS + ChooseRelocationOffsetDelta() : base_address); + MemMap image_reservation = + ReserveBootImageMemory(addr, image_reservation_size + extra_reservation_size, error_msg); + if (!image_reservation.IsValid()) { return false; } - ArrayRef<const std::string> provided_locations(boot_class_path_locations_.data(), - system_hdr.GetComponentCount()); - std::vector<std::string> locations = - ExpandMultiImageLocations(provided_locations, image_location_); - std::vector<std::string> filenames = - ExpandMultiImageLocations(provided_locations, filename); - DCHECK_EQ(locations.size(), filenames.size()); + // Load components. std::vector<std::unique_ptr<ImageSpace>> spaces; - spaces.reserve(locations.size()); - for (std::size_t i = 0u, size = locations.size(); i != size; ++i) { - spaces.push_back(Load(locations[i], filenames[i], logger, &image_reservation, error_msg)); - const ImageSpace* space = spaces.back().get(); - if (space == nullptr) { - return false; - } - uint32_t expected_component_count = (i == 0u) ? system_hdr.GetComponentCount() : 0u; - uint32_t expected_reservation_size = (i == 0u) ? system_hdr.GetImageReservationSize() : 0u; - if (!Loader::CheckImageReservationSize(*space, expected_reservation_size, error_msg) || - !Loader::CheckImageComponentCount(*space, expected_component_count, error_msg)) { - return false; + spaces.reserve(image_component_count); + size_t max_image_space_dependencies = 0u; + for (size_t i = 0, num_chunks = chunks.size(); i != num_chunks; ++i) { + const BootImageLayout::ImageChunk& chunk = chunks[i]; + std::string extension_error_msg; + uint8_t* old_reservation_begin = image_reservation.Begin(); + size_t old_reservation_size = image_reservation.Size(); + DCHECK_LE(chunk.reservation_size, old_reservation_size); + if (!LoadComponents(chunk, + validate_oat_file, + max_image_space_dependencies, + logger, + &spaces, + &image_reservation, + (i == 0) ? error_msg : &extension_error_msg)) { + // Failed to load the chunk. If this is the primary boot image, report the error. + if (i == 0) { + return false; + } + // For extension, shrink the reservation (and remap if needed, see below). + size_t new_reservation_size = old_reservation_size - chunk.reservation_size; + if (new_reservation_size == 0u) { + DCHECK_EQ(extra_reservation_size, 0u); + DCHECK_EQ(i + 1u, num_chunks); + image_reservation.Reset(); + } else if (old_reservation_begin != image_reservation.Begin()) { + // Part of the image reservation has been used and then unmapped when + // rollling back the partial boot image extension load. Try to remap + // the image reservation. As this should be running single-threaded, + // the address range should still be available to mmap(). + image_reservation.Reset(); + std::string remap_error_msg; + image_reservation = ReserveBootImageMemory(old_reservation_begin, + new_reservation_size, + &remap_error_msg); + if (!image_reservation.IsValid()) { + *error_msg = StringPrintf("Failed to remap boot image reservation after failing " + "to load boot image extension (%s: %s): %s", + boot_class_path_locations_[chunk.start_index].c_str(), + extension_error_msg.c_str(), + remap_error_msg.c_str()); + return false; + } + } else { + DCHECK_EQ(old_reservation_size, image_reservation.Size()); + image_reservation.SetSize(new_reservation_size); + } + LOG(ERROR) << "Failed to load boot image extension " + << boot_class_path_locations_[chunk.start_index] << ": " << extension_error_msg; } - } - for (size_t i = 0u, size = spaces.size(); i != size; ++i) { - std::string expected_boot_class_path = - (i == 0u) ? android::base::Join(provided_locations, ':') : std::string(); - if (!OpenOatFile(spaces[i].get(), - boot_class_path_[i], - expected_boot_class_path, - validate_oat_file, - logger, - &image_reservation, - error_msg)) { - return false; + // Update `max_image_space_dependencies` if all previous BCP components + // were covered and loading the current chunk succeeded. + if (max_image_space_dependencies == chunk.start_index && + spaces.size() == chunk.start_index + chunk.component_count) { + max_image_space_dependencies = chunk.start_index + chunk.component_count; } } - if (!CheckReservationExhausted(image_reservation, error_msg)) { + + MemMap local_extra_reservation; + if (!RemapExtraReservation(extra_reservation_size, + &image_reservation, + &local_extra_reservation, + error_msg)) { return false; } @@ -1706,6 +2359,7 @@ class ImageSpace::BootImageLoader { simple_relocate_visitor); // Retrieve the Class.class, Method.class and Constructor.class needed in the loops below. + ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots; ObjPtr<mirror::Class> class_class; ObjPtr<mirror::Class> method_class; ObjPtr<mirror::Class> constructor_class; @@ -1720,9 +2374,8 @@ class ImageSpace::BootImageLoader { kExtension ? source_size - image_size : image_size); int32_t class_roots_index = enum_cast<int32_t>(ImageHeader::kClassRoots); DCHECK_LT(class_roots_index, image_roots->GetLength<kVerifyNone>()); - ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots = - ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor( - image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr())); + class_roots = ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(base_relocate_visitor( + image_roots->GetWithoutChecks<kVerifyNone>(class_roots_index).Ptr())); if (kExtension) { DCHECK(patched_objects->Test(class_roots.Ptr())); class_class = GetClassRoot<mirror::Class, kWithoutReadBarrier>(class_roots); @@ -1854,6 +2507,12 @@ class ImageSpace::BootImageLoader { pos += RoundUp(object->SizeOf<kVerifyNone>(), kObjectAlignment); } } + if (kIsDebugBuild && !kExtension) { + // We used just Test() instead of Set() above but we need to use Set() + // for class roots to satisfy a DCHECK() for extensions. + DCHECK(!patched_objects->Test(class_roots.Ptr())); + patched_objects->Set(class_roots.Ptr()); + } } void MaybeRelocateSpaces(const std::vector<std::unique_ptr<ImageSpace>>& spaces, @@ -1919,8 +2578,8 @@ class ImageSpace::BootImageLoader { bool OpenOatFile(ImageSpace* space, const std::string& dex_filename, - const std::string& expected_boot_class_path, bool validate_oat_file, + ArrayRef<const std::unique_ptr<ImageSpace>> dependencies, TimingLogger* logger, /*inout*/MemMap* image_reservation, /*out*/std::string* error_msg) { @@ -1967,13 +2626,47 @@ class ImageSpace::BootImageLoader { const char* oat_boot_class_path = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathKey); oat_boot_class_path = (oat_boot_class_path != nullptr) ? oat_boot_class_path : ""; - if (expected_boot_class_path != oat_boot_class_path) { - *error_msg = StringPrintf("Failed to match oat boot class path %s to expected " - "boot class path %s in image %s", - oat_boot_class_path, - expected_boot_class_path.c_str(), - space->GetName()); - return false; + const char* oat_boot_class_path_checksums = + oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); + oat_boot_class_path_checksums = + (oat_boot_class_path_checksums != nullptr) ? oat_boot_class_path_checksums : ""; + size_t component_count = image_header.GetComponentCount(); + if (component_count == 0u) { + if (oat_boot_class_path[0] != 0 || oat_boot_class_path_checksums[0] != 0) { + *error_msg = StringPrintf("Unexpected non-empty boot class path %s and/or checksums %s" + " in image %s", + oat_boot_class_path, + oat_boot_class_path_checksums, + space->GetName()); + return false; + } + } else if (dependencies.empty()) { + std::string expected_boot_class_path = android::base::Join(ArrayRef<const std::string>( + boot_class_path_locations_).SubArray(0u, component_count), ':'); + if (expected_boot_class_path != oat_boot_class_path) { + *error_msg = StringPrintf("Failed to match oat boot class path %s to expected " + "boot class path %s in image %s", + oat_boot_class_path, + expected_boot_class_path.c_str(), + space->GetName()); + return false; + } + } else { + std::string local_error_msg; + if (!VerifyBootClassPathChecksums( + oat_boot_class_path_checksums, + oat_boot_class_path, + dependencies, + ArrayRef<const std::string>(boot_class_path_locations_), + ArrayRef<const std::string>(boot_class_path_), + &local_error_msg)) { + *error_msg = StringPrintf("Failed to verify BCP %s with checksums %s in image %s: %s", + oat_boot_class_path, + oat_boot_class_path_checksums, + space->GetName(), + local_error_msg.c_str()); + return false; + } } ptrdiff_t relocation_diff = space->Begin() - image_header.GetImageBegin(); CHECK(image_header.GetOatDataBegin() != nullptr); @@ -2000,65 +2693,153 @@ class ImageSpace::BootImageLoader { return true; } - bool ReserveBootImageMemory(uint32_t reservation_size, - uint32_t image_start, - size_t extra_reservation_size, - /*out*/MemMap* image_reservation, - /*out*/MemMap* extra_reservation, - /*out*/std::string* error_msg) { - DCHECK_ALIGNED(reservation_size, kPageSize); - DCHECK_ALIGNED(image_start, kPageSize); - DCHECK(!image_reservation->IsValid()); - DCHECK_LT(extra_reservation_size, std::numeric_limits<uint32_t>::max() - reservation_size); - size_t total_size = reservation_size + extra_reservation_size; - // If relocating, choose a random address for ALSR. - uint32_t addr = relocate_ ? ART_BASE_ADDRESS + ChooseRelocationOffsetDelta() : image_start; - *image_reservation = - MemMap::MapAnonymous("Boot image reservation", - reinterpret_cast32<uint8_t*>(addr), - total_size, - PROT_NONE, - /*low_4gb=*/ true, - /*reuse=*/ false, - /*reservation=*/ nullptr, - error_msg); - if (!image_reservation->IsValid()) { + bool LoadComponents(const BootImageLayout::ImageChunk& chunk, + bool validate_oat_file, + size_t max_image_space_dependencies, + TimingLogger* logger, + /*inout*/std::vector<std::unique_ptr<ImageSpace>>* spaces, + /*inout*/MemMap* image_reservation, + /*out*/std::string* error_msg) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Make sure we destroy the spaces we created if we're returning an error. + // Note that this can unmap part of the original `image_reservation`. + class Guard { + public: + explicit Guard(std::vector<std::unique_ptr<ImageSpace>>* spaces_in) + : spaces_(spaces_in), committed_(spaces_->size()) {} + void Commit() { + DCHECK_LT(committed_, spaces_->size()); + committed_ = spaces_->size(); + } + ~Guard() { + DCHECK_LE(committed_, spaces_->size()); + spaces_->resize(committed_); + } + private: + std::vector<std::unique_ptr<ImageSpace>>* const spaces_; + size_t committed_; + }; + Guard guard(spaces); + + bool is_extension = (chunk.start_index != 0u); + DCHECK_NE(spaces->empty(), is_extension); + if (max_image_space_dependencies < chunk.boot_image_component_count) { + DCHECK(is_extension); + *error_msg = StringPrintf("Missing dependencies for extension component %s, %zu < %u", + boot_class_path_locations_[chunk.start_index].c_str(), + max_image_space_dependencies, + chunk.boot_image_component_count); return false; } - DCHECK(!extra_reservation->IsValid()); - if (extra_reservation_size != 0u) { - DCHECK_ALIGNED(extra_reservation_size, kPageSize); - DCHECK_LT(extra_reservation_size, image_reservation->Size()); - uint8_t* split = image_reservation->End() - extra_reservation_size; - *extra_reservation = image_reservation->RemapAtEnd(split, - "Boot image extra reservation", - PROT_NONE, - error_msg); - if (!extra_reservation->IsValid()) { + ArrayRef<const std::string> requested_bcp_locations = + ArrayRef<const std::string>(boot_class_path_locations_).SubArray( + chunk.start_index, chunk.component_count); + std::vector<std::string> locations = + ExpandMultiImageLocations(requested_bcp_locations, chunk.base_location, is_extension); + std::vector<std::string> filenames = + ExpandMultiImageLocations(requested_bcp_locations, chunk.base_filename, is_extension); + DCHECK_EQ(locations.size(), filenames.size()); + for (std::size_t i = 0u, size = locations.size(); i != size; ++i) { + spaces->push_back(Load(locations[i], filenames[i], logger, image_reservation, error_msg)); + const ImageSpace* space = spaces->back().get(); + if (space == nullptr) { + return false; + } + uint32_t expected_component_count = (i == 0u) ? chunk.component_count : 0u; + uint32_t expected_reservation_size = (i == 0u) ? chunk.reservation_size : 0u; + if (!Loader::CheckImageReservationSize(*space, expected_reservation_size, error_msg) || + !Loader::CheckImageComponentCount(*space, expected_component_count, error_msg)) { + return false; + } + const ImageHeader& header = space->GetImageHeader(); + if (i == 0 && (chunk.checksum != header.GetImageChecksum() || + chunk.boot_image_component_count != header.GetBootImageComponentCount() || + chunk.boot_image_checksum != header.GetBootImageChecksum())) { + *error_msg = StringPrintf("Image header modified since previously read from %s; " + "checksum: 0x%08x -> 0x%08x," + "boot_image_component_count: %u -> %u, " + "boot_image_checksum: 0x%08x -> 0x%08x", + space->GetImageFilename().c_str(), + chunk.checksum, + header.GetImageChecksum(), + chunk.boot_image_component_count, + header.GetBootImageComponentCount(), + chunk.boot_image_checksum, + header.GetBootImageChecksum()); + return false; + } + } + DCHECK_GE(max_image_space_dependencies, chunk.boot_image_component_count); + ArrayRef<const std::unique_ptr<ImageSpace>> dependencies = + ArrayRef<const std::unique_ptr<ImageSpace>>(*spaces).SubArray( + /*pos=*/ 0u, chunk.boot_image_component_count); + for (std::size_t i = 0u, size = locations.size(); i != size; ++i) { + ImageSpace* space = (*spaces)[spaces->size() - chunk.component_count + i].get(); + if (!OpenOatFile(space, + boot_class_path_[chunk.start_index + i], + validate_oat_file, + dependencies, + logger, + image_reservation, + error_msg)) { return false; } } + guard.Commit(); return true; } - bool CheckReservationExhausted(const MemMap& image_reservation, /*out*/std::string* error_msg) { - if (image_reservation.IsValid()) { - *error_msg = StringPrintf("Excessive image reservation after loading boot image: %p-%p", - image_reservation.Begin(), - image_reservation.End()); + MemMap ReserveBootImageMemory(uint8_t* addr, + uint32_t reservation_size, + /*out*/std::string* error_msg) { + DCHECK_ALIGNED(reservation_size, kPageSize); + DCHECK_ALIGNED(addr, kPageSize); + return MemMap::MapAnonymous("Boot image reservation", + addr, + reservation_size, + PROT_NONE, + /*low_4gb=*/ true, + /*reuse=*/ false, + /*reservation=*/ nullptr, + error_msg); + } + + bool RemapExtraReservation(size_t extra_reservation_size, + /*inout*/MemMap* image_reservation, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) { + DCHECK_ALIGNED(extra_reservation_size, kPageSize); + DCHECK(!extra_reservation->IsValid()); + size_t expected_size = image_reservation->IsValid() ? image_reservation->Size() : 0u; + if (extra_reservation_size != expected_size) { + *error_msg = StringPrintf("Image reservation mismatch after loading boot image: %zu != %zu", + extra_reservation_size, + expected_size); return false; } + if (extra_reservation_size != 0u) { + DCHECK(image_reservation->IsValid()); + DCHECK_EQ(extra_reservation_size, image_reservation->Size()); + *extra_reservation = image_reservation->RemapAtEnd(image_reservation->Begin(), + "Boot image extra reservation", + PROT_NONE, + error_msg); + if (!extra_reservation->IsValid()) { + return false; + } + } + DCHECK(!image_reservation->IsValid()); return true; } - const std::vector<std::string>& boot_class_path_; - const std::vector<std::string>& boot_class_path_locations_; - const std::string& image_location_; - InstructionSet image_isa_; - bool relocate_; - bool executable_; - bool is_zygote_; + const ArrayRef<const std::string> boot_class_path_; + const ArrayRef<const std::string> boot_class_path_locations_; + const std::string image_location_; + const InstructionSet image_isa_; + const bool relocate_; + const bool executable_; + const bool is_zygote_; bool has_system_; bool has_cache_; bool is_global_cache_; @@ -2067,6 +2848,68 @@ class ImageSpace::BootImageLoader { std::string cache_filename_; }; +bool ImageSpace::BootImageLoader::LoadFromSystem( + bool validate_oat_file, + size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) { + TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image)); + + BootImageLayout layout(image_location_, boot_class_path_); + if (!layout.LoadFromSystem(image_isa_, error_msg)) { + return false; + } + + if (!LoadImage(layout, + validate_oat_file, + extra_reservation_size, + &logger, + boot_image_spaces, + extra_reservation, + error_msg)) { + return false; + } + + if (VLOG_IS_ON(image)) { + LOG(INFO) << "ImageSpace::BootImageLoader::LoadFromSystem exiting " + << boot_image_spaces->front(); + logger.Dump(LOG_STREAM(INFO)); + } + return true; +} + +bool ImageSpace::BootImageLoader::LoadFromDalvikCache( + bool validate_oat_file, + size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) { + TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image)); + DCHECK(DalvikCacheExists()); + + BootImageLayout layout(image_location_, boot_class_path_); + if (!layout.LoadFromDalvikCache(dalvik_cache_, error_msg)) { + return false; + } + if (!LoadImage(layout, + validate_oat_file, + extra_reservation_size, + &logger, + boot_image_spaces, + extra_reservation, + error_msg)) { + return false; + } + + if (VLOG_IS_ON(image)) { + LOG(INFO) << "ImageSpace::BootImageLoader::LoadFromDalvikCache exiting " + << boot_image_spaces->front(); + logger.Dump(LOG_STREAM(INFO)); + } + return true; +} + static constexpr uint64_t kLowSpaceValue = 50 * MB; static constexpr uint64_t kTmpFsSentinelValue = 384 * MB; @@ -2112,7 +2955,7 @@ bool ImageSpace::LoadBootImage( bool executable, bool is_zygote, size_t extra_reservation_size, - /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, /*out*/MemMap* extra_reservation) { ScopedTrace trace(__FUNCTION__); @@ -2355,53 +3198,159 @@ bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg return true; } -std::string ImageSpace::GetBootClassPathChecksums(ArrayRef<const std::string> boot_class_path, - const std::string& image_location, - InstructionSet image_isa, - ImageSpaceLoadingOrder order, - /*out*/std::string* error_msg) { - std::string system_filename; - bool has_system = false; - std::string cache_filename; - bool has_cache = false; - bool dalvik_cache_exists = false; - bool is_global_cache = false; - if (!FindImageFilename(image_location.c_str(), - image_isa, - &system_filename, - &has_system, - &cache_filename, - &dalvik_cache_exists, - &has_cache, - &is_global_cache)) { - *error_msg = StringPrintf("Unable to find image file for %s and %s", - image_location.c_str(), - GetInstructionSetString(image_isa)); - return std::string(); - } - - DCHECK(has_system || has_cache); - const std::string& filename = (order == ImageSpaceLoadingOrder::kSystemFirst) - ? (has_system ? system_filename : cache_filename) - : (has_cache ? cache_filename : system_filename); - ImageHeader header; - if (!ReadSpecificImageHeader(filename.c_str(), &header, error_msg)) { - return std::string(); +std::string ImageSpace::GetBootClassPathChecksums( + ArrayRef<ImageSpace* const> image_spaces, + ArrayRef<const DexFile* const> boot_class_path) { + DCHECK(!boot_class_path.empty()); + size_t bcp_pos = 0u; + std::string boot_image_checksum; + + for (size_t image_pos = 0u, size = image_spaces.size(); image_pos != size; ) { + const ImageSpace* main_space = image_spaces[image_pos]; + // Caller must make sure that the image spaces correspond to the head of the BCP. + DCHECK_NE(main_space->oat_file_non_owned_->GetOatDexFiles().size(), 0u); + DCHECK_EQ(main_space->oat_file_non_owned_->GetOatDexFiles()[0]->GetDexFileLocation(), + boot_class_path[bcp_pos]->GetLocation()); + const ImageHeader& current_header = main_space->GetImageHeader(); + uint32_t component_count = current_header.GetComponentCount(); + DCHECK_NE(component_count, 0u); + DCHECK_LE(component_count, image_spaces.size() - image_pos); + if (image_pos != 0u) { + boot_image_checksum += ':'; + } + AppendImageChecksum(component_count, current_header.GetImageChecksum(), &boot_image_checksum); + for (size_t component_index = 0; component_index != component_count; ++component_index) { + const ImageSpace* space = image_spaces[image_pos + component_index]; + const OatFile* oat_file = space->oat_file_non_owned_; + size_t num_dex_files = oat_file->GetOatDexFiles().size(); + if (kIsDebugBuild) { + CHECK_NE(num_dex_files, 0u); + CHECK_LE(oat_file->GetOatDexFiles().size(), boot_class_path.size() - bcp_pos); + for (size_t i = 0; i != num_dex_files; ++i) { + CHECK_EQ(oat_file->GetOatDexFiles()[i]->GetDexFileLocation(), + boot_class_path[bcp_pos + i]->GetLocation()); + } + } + bcp_pos += num_dex_files; + } + image_pos += component_count; } - if (header.GetComponentCount() == 0u || header.GetComponentCount() > boot_class_path.size()) { - *error_msg = StringPrintf("Unexpected component count in %s, received %u, " - "expected non-zero and <= %zu", - filename.c_str(), - header.GetComponentCount(), - boot_class_path.size()); - return std::string(); + + ArrayRef<const DexFile* const> boot_class_path_tail = + ArrayRef<const DexFile* const>(boot_class_path).SubArray(bcp_pos); + DCHECK(boot_class_path_tail.empty() || + !DexFileLoader::IsMultiDexLocation(boot_class_path_tail.front()->GetLocation().c_str())); + for (const DexFile* dex_file : boot_class_path_tail) { + if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) { + if (!boot_image_checksum.empty()) { + boot_image_checksum += ':'; + } + boot_image_checksum += kDexFileChecksumPrefix; + } + StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum()); + } + return boot_image_checksum; +} + +static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path, + ArrayRef<const std::string> boot_class_path, + /*out*/std::string* error_msg) { + // Check that the oat BCP is a prefix of current BCP locations and count components. + size_t component_count = 0u; + std::string_view remaining_bcp(oat_boot_class_path); + bool bcp_ok = false; + for (const std::string& location : boot_class_path) { + if (!StartsWith(remaining_bcp, location)) { + break; + } + remaining_bcp.remove_prefix(location.size()); + ++component_count; + if (remaining_bcp.empty()) { + bcp_ok = true; + break; + } + if (!StartsWith(remaining_bcp, ":")) { + break; + } + remaining_bcp.remove_prefix(1u); + } + if (!bcp_ok) { + *error_msg = StringPrintf("Oat boot class path (%s) is not a prefix of" + " runtime boot class path (%s)", + std::string(oat_boot_class_path).c_str(), + android::base::Join(boot_class_path, ':').c_str()); + return static_cast<size_t>(-1); + } + return component_count; +} + +bool ImageSpace::VerifyBootClassPathChecksums(std::string_view oat_checksums, + std::string_view oat_boot_class_path, + const std::string& image_location, + ArrayRef<const std::string> boot_class_path_locations, + ArrayRef<const std::string> boot_class_path, + InstructionSet image_isa, + ImageSpaceLoadingOrder order, + /*out*/std::string* error_msg) { + if (oat_checksums.empty() || oat_boot_class_path.empty()) { + *error_msg = oat_checksums.empty() ? "Empty checksums." : "Empty boot class path."; + return false; + } + + DCHECK_EQ(boot_class_path_locations.size(), boot_class_path.size()); + size_t bcp_size = + CheckAndCountBCPComponents(oat_boot_class_path, boot_class_path_locations, error_msg); + if (bcp_size == static_cast<size_t>(-1)) { + DCHECK(!error_msg->empty()); + return false; + } + + size_t bcp_pos = 0u; + if (StartsWith(oat_checksums, "i")) { + // Use only the matching part of the BCP for validation. + BootImageLayout layout(image_location, boot_class_path.SubArray(/*pos=*/ 0u, bcp_size)); + std::string primary_image_location = layout.GetPrimaryImageLocation(); + std::string system_filename; + bool has_system = false; + std::string cache_filename; + bool has_cache = false; + bool dalvik_cache_exists = false; + bool is_global_cache = false; + if (!FindImageFilename(primary_image_location.c_str(), + image_isa, + &system_filename, + &has_system, + &cache_filename, + &dalvik_cache_exists, + &has_cache, + &is_global_cache)) { + *error_msg = StringPrintf("Unable to find image file for %s and %s", + image_location.c_str(), + GetInstructionSetString(image_isa)); + return false; + } + + DCHECK(has_system || has_cache); + bool use_system = (order == ImageSpaceLoadingOrder::kSystemFirst) ? has_system : !has_cache; + bool image_checksums_ok = use_system + ? layout.ValidateFromSystem(image_isa, &oat_checksums, error_msg) + : layout.ValidateFromDalvikCache(cache_filename, &oat_checksums, error_msg); + if (!image_checksums_ok) { + return false; + } + bcp_pos = layout.GetNextBcpIndex(); } - std::string boot_image_checksum = - StringPrintf("i;%d/%08x", header.GetComponentCount(), header.GetImageChecksum()); - ArrayRef<const std::string> boot_class_path_tail = - ArrayRef<const std::string>(boot_class_path).SubArray(header.GetComponentCount()); - for (const std::string& bcp_filename : boot_class_path_tail) { + for ( ; bcp_pos != bcp_size; ++bcp_pos) { + static_assert(ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check."); + if (!StartsWith(oat_checksums, "d")) { + *error_msg = StringPrintf("Missing dex checksums, expected %s to start with 'd'", + std::string(oat_checksums).c_str()); + return false; + } + oat_checksums.remove_prefix(1u); + + const std::string& bcp_filename = boot_class_path[bcp_pos]; std::vector<std::unique_ptr<const DexFile>> dex_files; const ArtDexFileLoader dex_file_loader; if (!dex_file_loader.Open(bcp_filename.c_str(), @@ -2410,65 +3359,123 @@ std::string ImageSpace::GetBootClassPathChecksums(ArrayRef<const std::string> bo /*verify_checksum=*/ false, error_msg, &dex_files)) { - return std::string(); + return false; } DCHECK(!dex_files.empty()); - StringAppendF(&boot_image_checksum, ":d"); for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { - StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum()); + std::string dex_file_checksum = StringPrintf("/%08x", dex_file->GetLocationChecksum()); + if (!StartsWith(oat_checksums, dex_file_checksum)) { + *error_msg = StringPrintf("Dex checksum mismatch, expected %s to start with %s", + std::string(oat_checksums).c_str(), + dex_file_checksum.c_str()); + return false; + } + oat_checksums.remove_prefix(dex_file_checksum.size()); + } + if (bcp_pos + 1u != bcp_size) { + if (!StartsWith(oat_checksums, ":")) { + *error_msg = StringPrintf("Missing ':' separator at start of %s", + std::string(oat_checksums).c_str()); + return false; + } } } - return boot_image_checksum; + if (!oat_checksums.empty()) { + *error_msg = StringPrintf("Checksum too long, unexpected tail %s", + std::string(oat_checksums).c_str()); + return false; + } + return true; } -std::string ImageSpace::GetBootClassPathChecksums( - const std::vector<ImageSpace*>& image_spaces, - const std::vector<const DexFile*>& boot_class_path) { - size_t pos = 0u; - std::string boot_image_checksum; +bool ImageSpace::VerifyBootClassPathChecksums( + std::string_view oat_checksums, + std::string_view oat_boot_class_path, + ArrayRef<const std::unique_ptr<ImageSpace>> image_spaces, + ArrayRef<const std::string> boot_class_path_locations, + ArrayRef<const std::string> boot_class_path, + /*out*/std::string* error_msg) { + DCHECK_EQ(boot_class_path.size(), boot_class_path_locations.size()); + DCHECK_GE(boot_class_path_locations.size(), image_spaces.size()); + if (oat_checksums.empty() || oat_boot_class_path.empty()) { + *error_msg = oat_checksums.empty() ? "Empty checksums." : "Empty boot class path."; + return false; + } - if (!image_spaces.empty()) { - const ImageHeader& primary_header = image_spaces.front()->GetImageHeader(); - uint32_t component_count = primary_header.GetComponentCount(); - DCHECK_EQ(component_count, image_spaces.size()); - boot_image_checksum = - StringPrintf("i;%d/%08x", component_count, primary_header.GetImageChecksum()); - for (const ImageSpace* space : image_spaces) { - size_t num_dex_files = space->oat_file_non_owned_->GetOatDexFiles().size(); - if (kIsDebugBuild) { + size_t oat_bcp_size = + CheckAndCountBCPComponents(oat_boot_class_path, boot_class_path_locations, error_msg); + if (oat_bcp_size == static_cast<size_t>(-1)) { + DCHECK(!error_msg->empty()); + return false; + } + const size_t num_image_spaces = image_spaces.size(); + if (num_image_spaces != oat_bcp_size) { + *error_msg = StringPrintf("Image header records more dependencies (%zu) than BCP (%zu)", + num_image_spaces, + oat_bcp_size); + return false; + } + + // Verify image checksums. + size_t image_pos = 0u; + while (image_pos != num_image_spaces && StartsWith(oat_checksums, "i")) { + // Verify the current image checksum. + const ImageHeader& current_header = image_spaces[image_pos]->GetImageHeader(); + uint32_t component_count = current_header.GetComponentCount(); + DCHECK_NE(component_count, 0u); + DCHECK_LE(component_count, image_spaces.size() - image_pos); + uint32_t checksum = current_header.GetImageChecksum(); + if (!CheckAndRemoveImageChecksum(component_count, checksum, &oat_checksums, error_msg)) { + DCHECK(!error_msg->empty()); + return false; + } + + if (kIsDebugBuild) { + for (size_t component_index = 0; component_index != component_count; ++component_index) { + const OatFile* oat_file = image_spaces[image_pos + component_index]->oat_file_non_owned_; + size_t num_dex_files = oat_file->GetOatDexFiles().size(); CHECK_NE(num_dex_files, 0u); - CHECK_LE(space->oat_file_non_owned_->GetOatDexFiles().size(), boot_class_path.size() - pos); - for (size_t i = 0; i != num_dex_files; ++i) { - CHECK_EQ(space->oat_file_non_owned_->GetOatDexFiles()[i]->GetDexFileLocation(), - boot_class_path[pos + i]->GetLocation()); + const std::string main_location = oat_file->GetOatDexFiles()[0]->GetDexFileLocation(); + // TODO: Get rid of the weird ResolveRelativeEncodedDexLocation() stuff from oat_file.cc + // and enable this check: + // CHECK_EQ(main_location, boot_class_path_locations[image_pos + component_index]); + CHECK(!DexFileLoader::IsMultiDexLocation(main_location.c_str())); + for (size_t i = 1u; i != num_dex_files; ++i) { + CHECK(DexFileLoader::IsMultiDexLocation( + oat_file->GetOatDexFiles()[i]->GetDexFileLocation().c_str())); } } - pos += num_dex_files; } - } - ArrayRef<const DexFile* const> boot_class_path_tail = - ArrayRef<const DexFile* const>(boot_class_path).SubArray(pos); - DCHECK(boot_class_path_tail.empty() || - !DexFileLoader::IsMultiDexLocation(boot_class_path_tail.front()->GetLocation().c_str())); - for (const DexFile* dex_file : boot_class_path_tail) { - if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) { - StringAppendF(&boot_image_checksum, boot_image_checksum.empty() ? "d" : ":d"); + image_pos += component_count; + + if (!StartsWith(oat_checksums, ":")) { + // Check that we've reached the end of checksums and BCP. + if (!oat_checksums.empty()) { + *error_msg = StringPrintf("Expected ':' separator or end of checksums, remaining %s.", + std::string(oat_checksums).c_str()); + return false; + } + if (image_pos != oat_bcp_size) { + *error_msg = StringPrintf("Component count mismatch between checksums (%zu) and BCP (%zu)", + image_pos, + oat_bcp_size); + return false; + } + return true; } - StringAppendF(&boot_image_checksum, "/%08x", dex_file->GetLocationChecksum()); + oat_checksums.remove_prefix(1u); } - return boot_image_checksum; -} -std::vector<std::string> ImageSpace::ExpandMultiImageLocations( - const std::vector<std::string>& dex_locations, - const std::string& image_location) { - return ExpandMultiImageLocations(ArrayRef<const std::string>(dex_locations), image_location); + // We do not allow dependencies of extensions on dex files. That would require + // interleaving the loading of the images with opening the other BCP dex files. + return false; } std::vector<std::string> ImageSpace::ExpandMultiImageLocations( ArrayRef<const std::string> dex_locations, - const std::string& image_location) { + const std::string& image_location, + bool boot_image_extension) { DCHECK(!dex_locations.empty()); // Find the path. @@ -2497,10 +3504,14 @@ std::vector<std::string> ImageSpace::ExpandMultiImageLocations( std::vector<std::string> locations; locations.reserve(dex_locations.size()); - locations.push_back(image_location); + size_t start_index = 0u; + if (!boot_image_extension) { + start_index = 1u; + locations.push_back(image_location); + } - // Now create the other names. Use a counted loop to skip the first one. - for (size_t i = 1u; i < dex_locations.size(); ++i) { + // Now create the other names. Use a counted loop to skip the first one if needed. + for (size_t i = start_index; i < dex_locations.size(); ++i) { // Replace path with `base` (i.e. image path and prefix) and replace the original // extension (if any) with `extension`. std::string name = dex_locations[i]; diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h index c1b5a81980..f56b42bca3 100644 --- a/runtime/gc/space/image_space.h +++ b/runtime/gc/space/image_space.h @@ -39,11 +39,61 @@ class ImageSpace : public MemMapSpace { return kSpaceTypeImageSpace; } - // Load boot image spaces from a primary image file for a specified instruction set. + // Load boot image spaces for specified boot class path, image location, instruction set, etc. // // On successful return, the loaded spaces are added to boot_image_spaces (which must be // empty on entry) and `extra_reservation` is set to the requested reservation located // after the end of the last loaded oat file. + // + // IMAGE LOCATION + // + // The "image location" is a colon-separated list that specifies one or more + // components by name and may also specify search paths for extensions + // corresponding to the remaining boot class path (BCP) extensions. + // + // The primary boot image can be specified as one of + // <path>/<base-name> + // <base-name> + // and the path of the first BCP component is used for the second form. + // + // Named extension specifications must correspond to an expansion of the + // <base-name> with a BCP component (for example boot.art with the BCP + // component name <jar-path>/framework.jar expands to boot-framework.art). + // They can be similarly specified as one of + // <ext-path>/<ext-name> + // <ext-name> + // and must be listed in the order of their corresponding BCP components. + // + // Search paths for remaining extensions can be specified after named + // components as one of + // <search-path>/* + // * + // where the second form means that the path of a particular BCP component + // should be used to search for that component's boot image extension. These + // paths will be searched in the specifed order. + // + // The actual filename shall be derived from the specified locations using + // `GetSystemImageFilename()` or `GetDalvikCacheFilename()`. + // + // Example image locations: + // /system/framework/boot.art + // - only primary boot image with full path. + // boot.art:boot-framework.art + // - primary and one extension, use BCP component paths. + // /apex/com.android.art/boot.art:* + // - primary with exact location, search for the rest based on BCP + // component paths. + // boot.art:/system/framework/* + // - primary based on BCP component path, search for extensions in + // /system/framework. + // /apex/com.android.art/boot.art:/system/framework/*:* + // - primary with exact location, search for extensions first in + // /system/framework, then in the corresponding BCP component path. + // /apex/com.android.art/boot.art:*:/system/framework/* + // - primary with exact location, search for extensions first in the + // corresponding BCP component path and then in /system/framework. + // /apex/com.android.art/boot.art:*:boot-framework.jar + // - invalid, named components may not follow search paths. static bool LoadBootImage( const std::vector<std::string>& boot_class_path, const std::vector<std::string>& boot_class_path_locations, @@ -54,7 +104,7 @@ class ImageSpace : public MemMapSpace { bool executable, bool is_zygote, size_t extra_reservation_size, - /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/std::vector<std::unique_ptr<ImageSpace>>* boot_image_spaces, /*out*/MemMap* extra_reservation) REQUIRES_SHARED(Locks::mutator_lock_); // Try to open an existing app image space. @@ -137,24 +187,39 @@ class ImageSpace : public MemMapSpace { // The leading character in a dex file checksum part of boot class path checkums. static constexpr char kDexFileChecksumPrefix = 'd'; - // Returns the checksums for the boot image and extra boot class path dex files, - // based on the boot class path, image location and ISA (may differ from the ISA of an - // initialized Runtime). The boot image and dex files do not need to be loaded in memory. - static std::string GetBootClassPathChecksums(ArrayRef<const std::string> boot_class_path, - const std::string& image_location, - InstructionSet image_isa, - ImageSpaceLoadingOrder order, - /*out*/std::string* error_msg); - - // Returns the checksums for the boot image and extra boot class path dex files, - // based on the boot image and boot class path dex files loaded in memory. - static std::string GetBootClassPathChecksums(const std::vector<ImageSpace*>& image_spaces, - const std::vector<const DexFile*>& boot_class_path); + // Returns the checksums for the boot image, extensions and extra boot class path dex files, + // based on the image spaces and boot class path dex files loaded in memory. + // The `image_spaces` must correspond to the head of the `boot_class_path`. + static std::string GetBootClassPathChecksums(ArrayRef<ImageSpace* const> image_spaces, + ArrayRef<const DexFile* const> boot_class_path); + + // Returns whether the checksums are valid for the given boot class path, + // image location and ISA (may differ from the ISA of an initialized Runtime). + // The boot image and dex files do not need to be loaded in memory. + static bool VerifyBootClassPathChecksums(std::string_view oat_checksums, + std::string_view oat_boot_class_path, + const std::string& image_location, + ArrayRef<const std::string> boot_class_path_locations, + ArrayRef<const std::string> boot_class_path, + InstructionSet image_isa, + ImageSpaceLoadingOrder order, + /*out*/std::string* error_msg); + + // Returns whether the oat checksums and boot class path description are valid + // for the given boot image spaces and boot class path. Used for boot image extensions. + static bool VerifyBootClassPathChecksums( + std::string_view oat_checksums, + std::string_view oat_boot_class_path, + ArrayRef<const std::unique_ptr<ImageSpace>> image_spaces, + ArrayRef<const std::string> boot_class_path_locations, + ArrayRef<const std::string> boot_class_path, + /*out*/std::string* error_msg); // Expand a single image location to multi-image locations based on the dex locations. static std::vector<std::string> ExpandMultiImageLocations( - const std::vector<std::string>& dex_locations, - const std::string& image_location); + ArrayRef<const std::string> dex_locations, + const std::string& image_location, + bool boot_image_extension = false); // Returns true if the dex checksums in the given oat file match the // checksums of the original dex files on disk. This is intended to be used @@ -218,11 +283,7 @@ class ImageSpace : public MemMapSpace { friend class Space; private: - // Internal overload that takes ArrayRef<> instead of vector<>. - static std::vector<std::string> ExpandMultiImageLocations( - ArrayRef<const std::string> dex_locations, - const std::string& image_location); - + class BootImageLayout; class BootImageLoader; template <typename ReferenceVisitor> class ClassTableVisitor; diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc index 98774bdf2d..6a9bdf6c79 100644 --- a/runtime/hidden_api.cc +++ b/runtime/hidden_api.cc @@ -435,15 +435,10 @@ bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod acce DCHECK(member != nullptr); Runtime* runtime = Runtime::Current(); - EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy(); - DCHECK(policy != EnforcementPolicy::kDisabled) + EnforcementPolicy hiddenApiPolicy = runtime->GetHiddenApiEnforcementPolicy(); + DCHECK(hiddenApiPolicy != EnforcementPolicy::kDisabled) << "Should never enter this function when access checks are completely disabled"; - const bool deny_access = - (policy == EnforcementPolicy::kEnabled) && - IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(), - api_list.GetMaxAllowedSdkVersion()); - MemberSignature member_signature(member); // Check for an exemption first. Exempted APIs are treated as white list. @@ -455,6 +450,18 @@ bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod acce return false; } + EnforcementPolicy testApiPolicy = runtime->GetTestApiEnforcementPolicy(); + + bool deny_access = false; + if (hiddenApiPolicy == EnforcementPolicy::kEnabled) { + if (testApiPolicy == EnforcementPolicy::kDisabled && api_list.IsTestApi()) { + deny_access = false; + } else { + deny_access = IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(), + api_list.GetMaxAllowedSdkVersion()); + } + } + if (access_method != AccessMethod::kNone) { // Print a log message with information about this class member access. // We do this if we're about to deny access, or the app is debuggable. diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index a21225b376..2ef3522eee 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -358,6 +358,7 @@ ALWAYS_INLINE inline uint32_t GetRuntimeFlags(ArtMethod* method) return 0u; case Intrinsics::kUnsafeGetLong: case Intrinsics::kFP16ToFloat: + case Intrinsics::kFP16ToHalf: return kAccCorePlatformApi; default: // Remaining intrinsics are public API. We DCHECK that in SetIntrinsic(). diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc index 70fafe6587..145bb07676 100644 --- a/runtime/hidden_api_test.cc +++ b/runtime/hidden_api_test.cc @@ -109,6 +109,7 @@ TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) { runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kJustWarn); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Whitelist()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxQ()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxP()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxO()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blacklist()), false); @@ -118,6 +119,7 @@ TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) { static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxO().GetMaxAllowedSdkVersion())); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Whitelist()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxQ()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxP()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxO()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blacklist()), true); @@ -127,6 +129,7 @@ TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) { static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxO().GetMaxAllowedSdkVersion()) + 1); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Whitelist()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxQ()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxP()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxO()), true); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blacklist()), true); @@ -136,9 +139,58 @@ TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) { static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxP().GetMaxAllowedSdkVersion()) + 1); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Whitelist()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxQ()), false); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxP()), true); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxO()), true); ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blacklist()), true); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled); + runtime_->SetTargetSdkVersion( + static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxQ().GetMaxAllowedSdkVersion()) + 1); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Whitelist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxQ()), true); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxP()), true); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::GreylistMaxO()), true); + ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::Blacklist()), true); +} + +TEST_F(HiddenApiTest, CheckTestApiEnforcement) { + ScopedObjectAccess soa(self_); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled); + runtime_->SetTargetSdkVersion( + static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxQ().GetMaxAllowedSdkVersion()) + 1); + + // Default case where all TestApis are treated like non-TestApi. + runtime_->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Whitelist()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxQ()), true); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxP()), true); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxO()), true); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Blacklist()), true); + + // A case where we want to allow access to TestApis. + runtime_->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDisabled); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Whitelist()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Greylist()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxQ()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxP()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::GreylistMaxO()), false); + ASSERT_EQ( + ShouldDenyAccess(hiddenapi::ApiList::TestApi() | hiddenapi::ApiList::Blacklist()), false); } TEST_F(HiddenApiTest, CheckMembersRead) { diff --git a/runtime/image.cc b/runtime/image.cc index 11fac590b0..06ba946549 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -29,7 +29,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '8', '\0' }; // FP16ToFloat intrinsic +const uint8_t ImageHeader::kImageVersion[] = { '0', '8', '0', '\0' }; // Chained checksums. ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t component_count, @@ -44,6 +44,8 @@ ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t oat_file_end, uint32_t boot_image_begin, uint32_t boot_image_size, + uint32_t boot_image_component_count, + uint32_t boot_image_checksum, uint32_t pointer_size) : image_reservation_size_(image_reservation_size), component_count_(component_count), @@ -57,6 +59,8 @@ ImageHeader::ImageHeader(uint32_t image_reservation_size, oat_file_end_(oat_file_end), boot_image_begin_(boot_image_begin), boot_image_size_(boot_image_size), + boot_image_component_count_(boot_image_component_count), + boot_image_checksum_(boot_image_checksum), image_roots_(image_roots), pointer_size_(pointer_size) { CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize)); @@ -93,6 +97,13 @@ void ImageHeader::RelocateBootImageReferences(int64_t delta) { } } +bool ImageHeader::IsAppImage() const { + // Unlike boot image and boot image extensions which include address space for + // oat files in their reservation size, app images are loaded separately from oat + // files and their reservation size is the image size rounded up to full page. + return image_reservation_size_ == RoundUp(image_size_, kPageSize); +} + bool ImageHeader::IsValid() const { if (memcmp(magic_, kImageMagic, sizeof(kImageMagic)) != 0) { return false; diff --git a/runtime/image.h b/runtime/image.h index 13bf112c99..12950a3591 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -137,6 +137,8 @@ class PACKED(8) ImageHeader { uint32_t oat_file_end, uint32_t boot_image_begin, uint32_t boot_image_size, + uint32_t boot_image_component_count, + uint32_t boot_image_checksum, uint32_t pointer_size); bool IsValid() const; @@ -350,15 +352,19 @@ class PACKED(8) ImageHeader { return boot_image_size_; } + uint32_t GetBootImageComponentCount() const { + return boot_image_component_count_; + } + + uint32_t GetBootImageChecksum() const { + return boot_image_checksum_; + } + uint64_t GetDataSize() const { return data_size_; } - bool IsAppImage() const { - // App images currently require a boot image, if the size is non zero then it is an app image - // header. - return boot_image_size_ != 0u; - } + bool IsAppImage() const; // Visit mirror::Objects in the section starting at base. // TODO: Delete base parameter if it is always equal to GetImageBegin. @@ -465,10 +471,15 @@ class PACKED(8) ImageHeader { // .so files. Used for positioning a following alloc spaces. uint32_t oat_file_end_ = 0u; - // Boot image begin and end (app image headers only). + // Boot image begin and end (only applies to boot image extension and app image headers). uint32_t boot_image_begin_ = 0u; uint32_t boot_image_size_ = 0u; // Includes heap (*.art) and code (.oat). + // Number of boot image components that this image depends on and their composite checksum + // (only applies to boot image extension and app image headers). + uint32_t boot_image_component_count_ = 0u; + uint32_t boot_image_checksum_ = 0u; + // Absolute address of an Object[] of objects needed to reinitialize from an image. uint32_t image_roots_ = 0u; diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc index 6b2d989cd3..3759225b91 100644 --- a/runtime/interpreter/interpreter_intrinsics.cc +++ b/runtime/interpreter/interpreter_intrinsics.cc @@ -574,6 +574,7 @@ bool MterpHandleIntrinsic(ShadowFrame* shadow_frame, UNIMPLEMENTED_CASE(CRC32UpdateBytes /* (I[BII)I */) UNIMPLEMENTED_CASE(CRC32UpdateByteBuffer /* (IJII)I */) UNIMPLEMENTED_CASE(FP16ToFloat /* (S)F */) + UNIMPLEMENTED_CASE(FP16ToHalf /* (F)S */) INTRINSIC_CASE(VarHandleFullFence) INTRINSIC_CASE(VarHandleAcquireFence) INTRINSIC_CASE(VarHandleReleaseFence) diff --git a/runtime/intrinsics_list.h b/runtime/intrinsics_list.h index 15ae309624..bb41ca732d 100644 --- a/runtime/intrinsics_list.h +++ b/runtime/intrinsics_list.h @@ -166,6 +166,7 @@ V(MemoryPokeLongNative, kStatic, kNeedsEnvironmentOrCache, kWriteSideEffects, kCanThrow, "Llibcore/io/Memory;", "pokeLongNative", "(JJ)V") \ V(MemoryPokeShortNative, kStatic, kNeedsEnvironmentOrCache, kWriteSideEffects, kCanThrow, "Llibcore/io/Memory;", "pokeShortNative", "(JS)V") \ V(FP16ToFloat, kStatic, kNeedsEnvironmentOrCache, kNoSideEffects, kNoThrow, "Llibcore/util/FP16;", "toFloat", "(S)F") \ + V(FP16ToHalf, kStatic, kNeedsEnvironmentOrCache, kNoSideEffects, kNoThrow, "Llibcore/util/FP16;", "toHalf", "(F)S") \ V(StringCharAt, kVirtual, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow, "Ljava/lang/String;", "charAt", "(I)C") \ V(StringCompareTo, kVirtual, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow, "Ljava/lang/String;", "compareTo", "(Ljava/lang/String;)I") \ V(StringEquals, kVirtual, kNeedsEnvironmentOrCache, kReadSideEffects, kCanThrow, "Ljava/lang/String;", "equals", "(Ljava/lang/Object;)Z") \ diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 11619c455f..f69d786299 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -215,7 +215,10 @@ Jit::Jit(JitCodeCache* code_cache, JitOptions* options) boot_completed_lock_("Jit::boot_completed_lock_"), cumulative_timings_("JIT timings"), memory_use_("Memory used for compilation", 16), - lock_("JIT memory use lock") {} + lock_("JIT memory use lock"), + zygote_mapping_methods_(), + fd_methods_(-1), + fd_methods_size_(0) {} Jit* Jit::Create(JitCodeCache* code_cache, JitOptions* options) { if (jit_load_ == nullptr) { @@ -589,6 +592,135 @@ void Jit::AddMemoryUsage(ArtMethod* method, size_t bytes) { memory_use_.AddValue(bytes); } +void Jit::NotifyZygoteCompilationDone() { + if (fd_methods_ == -1) { + return; + } + + size_t offset = 0; + for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) { + const ImageHeader& header = space->GetImageHeader(); + const ImageSection& section = header.GetMethodsSection(); + // Because mremap works at page boundaries, we can only handle methods + // within a page range. For methods that falls above or below the range, + // the child processes will copy their contents to their private mapping + // in `child_mapping_methods`. See `MapBootImageMethods`. + uint8_t* page_start = AlignUp(header.GetImageBegin() + section.Offset(), kPageSize); + uint8_t* page_end = + AlignDown(header.GetImageBegin() + section.Offset() + section.Size(), kPageSize); + if (page_end > page_start) { + uint64_t capacity = page_end - page_start; + memcpy(zygote_mapping_methods_.Begin() + offset, page_start, capacity); + offset += capacity; + } + } + + // Do an msync to ensure we are not affected by writes still being in caches. + if (msync(zygote_mapping_methods_.Begin(), fd_methods_size_, MS_SYNC) != 0) { + PLOG(WARNING) << "Failed to sync boot image methods memory"; + code_cache_->GetZygoteMap()->SetCompilationState(ZygoteCompilationState::kNotifiedFailure); + return; + } + + // We don't need the shared mapping anymore, and we need to drop it in case + // the file hasn't been sealed writable. + zygote_mapping_methods_ = MemMap::Invalid(); + + // Seal writes now. Zygote and children will map the memory private in order + // to write to it. + if (fcntl(fd_methods_, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_WRITE) == -1) { + PLOG(WARNING) << "Failed to seal boot image methods file descriptor"; + code_cache_->GetZygoteMap()->SetCompilationState(ZygoteCompilationState::kNotifiedFailure); + return; + } + + std::string error_str; + MemMap child_mapping_methods = MemMap::MapFile( + fd_methods_size_, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + fd_methods_, + /* start= */ 0, + /* low_4gb= */ false, + "boot-image-methods", + &error_str); + + if (!child_mapping_methods.IsValid()) { + LOG(WARNING) << "Failed to create child mapping of boot image methods: " << error_str; + code_cache_->GetZygoteMap()->SetCompilationState(ZygoteCompilationState::kNotifiedFailure); + return; + } + + // Ensure the contents are the same as before: there was a window between + // the memcpy and the sealing where other processes could have changed the + // contents. + // Note this would not be needed if we could have used F_SEAL_FUTURE_WRITE, + // see b/143833776. + offset = 0; + for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) { + const ImageHeader& header = space->GetImageHeader(); + const ImageSection& section = header.GetMethodsSection(); + // Because mremap works at page boundaries, we can only handle methods + // within a page range. For methods that falls above or below the range, + // the child processes will copy their contents to their private mapping + // in `child_mapping_methods`. See `MapBootImageMethods`. + uint8_t* page_start = AlignUp(header.GetImageBegin() + section.Offset(), kPageSize); + uint8_t* page_end = + AlignDown(header.GetImageBegin() + section.Offset() + section.Size(), kPageSize); + if (page_end > page_start) { + uint64_t capacity = page_end - page_start; + if (memcmp(child_mapping_methods.Begin() + offset, page_start, capacity) != 0) { + LOG(WARNING) << "Contents differ in boot image methods data"; + code_cache_->GetZygoteMap()->SetCompilationState( + ZygoteCompilationState::kNotifiedFailure); + return; + } + offset += capacity; + } + } + + // Future spawned processes don't need the fd anymore. + fd_methods_.reset(); + + // In order to have the zygote and children share the memory, we also remap + // the memory into the zygote process. + offset = 0; + for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) { + const ImageHeader& header = space->GetImageHeader(); + const ImageSection& section = header.GetMethodsSection(); + // Because mremap works at page boundaries, we can only handle methods + // within a page range. For methods that falls above or below the range, + // the child processes will copy their contents to their private mapping + // in `child_mapping_methods`. See `MapBootImageMethods`. + uint8_t* page_start = AlignUp(header.GetImageBegin() + section.Offset(), kPageSize); + uint8_t* page_end = + AlignDown(header.GetImageBegin() + section.Offset() + section.Size(), kPageSize); + if (page_end > page_start) { + uint64_t capacity = page_end - page_start; + if (mremap(child_mapping_methods.Begin() + offset, + capacity, + capacity, + MREMAP_FIXED | MREMAP_MAYMOVE, + page_start) == MAP_FAILED) { + // Failing to remap is safe as the process will just use the old + // contents. + PLOG(WARNING) << "Failed mremap of boot image methods of " << space->GetImageFilename(); + } + offset += capacity; + } + } + + LOG(INFO) << "Successfully notified child processes on sharing boot image methods"; + + // Mark that compilation of boot classpath is done, and memory can now be + // shared. Other processes will pick up this information. + code_cache_->GetZygoteMap()->SetCompilationState(ZygoteCompilationState::kNotifiedOk); + + // The private mapping created for this process has been mremaped. We can + // reset it. + child_mapping_methods.Reset(); +} + class JitCompileTask final : public Task { public: enum class TaskKind { @@ -691,38 +823,9 @@ class JitDoneCompilingProfileTask final : public SelfDeletingTask { } if (Runtime::Current()->IsZygote()) { - // Copy the boot image methods data to the mappings we created to share - // with the children. - Jit* jit = Runtime::Current()->GetJit(); - size_t offset = 0; - for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) { - const ImageHeader& header = space->GetImageHeader(); - const ImageSection& section = header.GetMethodsSection(); - // Because mremap works at page boundaries, we can only handle methods - // within a page range. For methods that falls above or below the range, - // the child processes will copy their contents to their private mapping - // in `child_mapping_methods_`. See `MapBootImageMethods`. - uint8_t* page_start = AlignUp(header.GetImageBegin() + section.Offset(), kPageSize); - uint8_t* page_end = - AlignDown(header.GetImageBegin() + section.Offset() + section.Size(), kPageSize); - if (page_end > page_start) { - uint64_t capacity = page_end - page_start; - memcpy(jit->GetZygoteMappingMethods().Begin() + offset, page_start, capacity); - // So the memory is shared, also map the memory into the zygote - // process. - if (mremap(jit->GetChildMappingMethods().Begin() + offset, - capacity, - capacity, - MREMAP_FIXED | MREMAP_MAYMOVE, - page_start) == MAP_FAILED) { - PLOG(WARNING) << "Failed mremap of boot image methods of " << space->GetImageFilename(); - } - offset += capacity; - } - } - // Mark that compilation of boot classpath is done. Other processes will - // pick up this boolean. - jit->GetCodeCache()->GetZygoteMap()->SetCompilationDone(); + // Record that we are done compiling the profile. + Runtime::Current()->GetJit()->GetCodeCache()->GetZygoteMap()->SetCompilationState( + ZygoteCompilationState::kDone); } } @@ -842,7 +945,28 @@ static void CopyIfDifferent(void* s1, const void* s2, size_t n) { } void Jit::MapBootImageMethods() { - if (!GetChildMappingMethods().IsValid()) { + CHECK_NE(fd_methods_.get(), -1); + if (!code_cache_->GetZygoteMap()->CanMapBootImageMethods()) { + LOG(WARNING) << "Not mapping boot image methods due to error from zygote"; + return; + } + + std::string error_str; + MemMap child_mapping_methods = MemMap::MapFile( + fd_methods_size_, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + fd_methods_, + /* start= */ 0, + /* low_4gb= */ false, + "boot-image-methods", + &error_str); + + // We don't need the fd anymore. + fd_methods_.reset(); + + if (!child_mapping_methods.IsValid()) { + LOG(WARNING) << "Failed to create child mapping of boot image methods: " << error_str; return; } size_t offset = 0; @@ -863,6 +987,10 @@ void Jit::MapBootImageMethods() { // such methods, we need their entrypoints to be stubs that do the // initialization check. header.VisitPackedArtMethods([&](ArtMethod& method) NO_THREAD_SAFETY_ANALYSIS { + // Methods in the boot image should never have their single + // implementation flag set (and therefore never have a `data_` pointing + // to an ArtMethod for single implementation). + CHECK(method.IsIntrinsic() || !method.HasSingleImplementationFlag()); if (method.IsRuntimeMethod()) { return; } @@ -898,7 +1026,7 @@ void Jit::MapBootImageMethods() { // For all the methods in the mapping, put the entrypoint to the // resolution stub. ArtMethod* new_method = reinterpret_cast<ArtMethod*>( - GetChildMappingMethods().Begin() + offset + (pointer - page_start)); + child_mapping_methods.Begin() + offset + (pointer - page_start)); const void* code = new_method->GetEntryPointFromQuickCompiledCode(); if (!class_linker->IsQuickGenericJniStub(code) && !class_linker->IsQuickToInterpreterBridge(code) && @@ -918,7 +1046,7 @@ void Jit::MapBootImageMethods() { // |/////////| -> copy -> |/////////| // | | | | // - CopyIfDifferent(GetChildMappingMethods().Begin() + offset, + CopyIfDifferent(child_mapping_methods.Begin() + offset, page_start, pointer + sizeof(ArtMethod) - page_start); } else if (pointer < page_end && (pointer + sizeof(ArtMethod)) > page_end) { @@ -933,14 +1061,14 @@ void Jit::MapBootImageMethods() { // section end --> ----------- // size_t bytes_to_copy = (page_end - pointer); - CopyIfDifferent(GetChildMappingMethods().Begin() + offset + capacity - bytes_to_copy, + CopyIfDifferent(child_mapping_methods.Begin() + offset + capacity - bytes_to_copy, page_end - bytes_to_copy, bytes_to_copy); } }, space->Begin(), kRuntimePointerSize); // Map the memory in the boot image range. - if (mremap(GetChildMappingMethods().Begin() + offset, + if (mremap(child_mapping_methods.Begin() + offset, capacity, capacity, MREMAP_FIXED | MREMAP_MAYMOVE, @@ -949,6 +1077,11 @@ void Jit::MapBootImageMethods() { } offset += capacity; } + + // The private mapping created for this process has been mremaped. We can + // reset it. + child_mapping_methods.Reset(); + LOG(INFO) << "Successfully mapped boot image methods"; } void Jit::CreateThreadPool() { @@ -990,7 +1123,7 @@ void Jit::CreateThreadPool() { // Start with '/boot' and end with '.art' to match the pattern recognized // by android_os_Debug.cpp for boot images. const char* name = "/boot-image-methods.art"; - unique_fd mem_fd = unique_fd(art::memfd_create(name, /* flags= */ 0)); + unique_fd mem_fd = unique_fd(art::memfd_create(name, /* flags= */ MFD_ALLOW_SEALING)); if (mem_fd.get() == -1) { PLOG(WARNING) << "Could not create boot image methods file descriptor"; return; @@ -1000,6 +1133,9 @@ void Jit::CreateThreadPool() { return; } std::string error_str; + + // Create the shared mapping eagerly, as this prevents other processes + // from adding the writable seal. zygote_mapping_methods_ = MemMap::MapFile( total_capacity, PROT_READ | PROT_WRITE, @@ -1020,21 +1156,17 @@ void Jit::CreateThreadPool() { return; } - child_mapping_methods_ = MemMap::MapFile( - total_capacity, - PROT_READ | PROT_WRITE, - MAP_PRIVATE, - mem_fd, - /* start= */ 0, - /* low_4gb= */ true, - "boot-image-methods", - &error_str); - - if (!child_mapping_methods_.IsValid()) { - LOG(WARNING) << "Failed to create child mapping of boot image methods: " << error_str; + // We should use the F_SEAL_FUTURE_WRITE flag, but this has unexpected + // behavior on private mappings after fork (the mapping becomes shared between + // parent and children), see b/143833776. + // We will seal the write once we are done writing to the shared mapping. + if (fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW) == -1) { + PLOG(WARNING) << "Failed to seal boot image methods file descriptor"; zygote_mapping_methods_ = MemMap(); return; } + fd_methods_ = unique_fd(mem_fd.release()); + fd_methods_size_ = total_capacity; } } } @@ -1425,8 +1557,27 @@ static void* RunPollingThread(void* arg) { Jit* jit = reinterpret_cast<Jit*>(arg); do { sleep(10); - } while (!jit->GetCodeCache()->GetZygoteMap()->IsCompilationDone()); - jit->MapBootImageMethods(); + } while (!jit->GetCodeCache()->GetZygoteMap()->IsCompilationNotified()); + + // We will suspend other threads: we can only do that if we're attached to the + // runtime. + Runtime* runtime = Runtime::Current(); + bool thread_attached = runtime->AttachCurrentThread( + "BootImagePollingThread", + /* as_daemon= */ true, + /* thread_group= */ nullptr, + /* create_peer= */ false); + CHECK(thread_attached); + + { + // Prevent other threads from running while we are remapping the boot image + // ArtMethod's. Native threads might still be running, but they cannot + // change the contents of ArtMethod's. + ScopedSuspendAll ssa(__FUNCTION__); + runtime->GetJit()->MapBootImageMethods(); + } + + Runtime::Current()->DetachCurrentThread(); return nullptr; } @@ -1437,8 +1588,7 @@ void Jit::PostForkChildAction(bool is_system_server, bool is_zygote) { tasks_after_boot_.clear(); } - if (Runtime::Current()->IsUsingApexBootImageLocation() && - !GetCodeCache()->GetZygoteMap()->IsCompilationDone()) { + if (Runtime::Current()->IsUsingApexBootImageLocation() && fd_methods_ != -1) { // Create a thread that will poll the status of zygote compilation, and map // the private mapping of boot image methods. zygote_mapping_methods_.ResetInForkedProcess(); @@ -1491,6 +1641,15 @@ void Jit::PostZygoteFork() { if (thread_pool_ == nullptr) { return; } + if (Runtime::Current()->IsZygote() && + code_cache_->GetZygoteMap()->IsCompilationDoneButNotNotified()) { + // Copy the boot image methods data to the mappings we created to share + // with the children. We do this here as we are the only thread running and + // we don't risk other threads concurrently updating the ArtMethod's. + CHECK_EQ(GetTaskCount(), 1); + NotifyZygoteCompilationDone(); + CHECK(code_cache_->GetZygoteMap()->IsCompilationNotified()); + } thread_pool_->CreateThreads(); } diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 68aa1dc35b..e5b77c2c7e 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -17,6 +17,8 @@ #ifndef ART_RUNTIME_JIT_JIT_H_ #define ART_RUNTIME_JIT_JIT_H_ +#include <android-base/unique_fd.h> + #include "base/histogram-inl.h" #include "base/macros.h" #include "base/mutex.h" @@ -374,16 +376,12 @@ class Jit { bool CanAssumeInitialized(ObjPtr<mirror::Class> cls, bool is_for_shared_region) const REQUIRES_SHARED(Locks::mutator_lock_); - const MemMap& GetZygoteMappingMethods() const { - return zygote_mapping_methods_; - } - - const MemMap& GetChildMappingMethods() const { - return child_mapping_methods_; - } - // Map boot image methods after all compilation in zygote has been done. - void MapBootImageMethods(); + void MapBootImageMethods() REQUIRES(Locks::mutator_lock_); + + // Notify to other processes that the zygote is done profile compiling boot + // class path methods. + void NotifyZygoteCompilationDone(); private: Jit(JitCodeCache* code_cache, JitOptions* options); @@ -434,16 +432,23 @@ class Jit { // In the JIT zygote configuration, after all compilation is done, the zygote // will copy its contents of the boot image to the zygote_mapping_methods_, - // which will be picked up by processes that will map child_mapping_methods_ + // which will be picked up by processes that will map the memory // in-place within the boot image mapping. // - // zygote_mapping_methods_ and child_mapping_methods_ point to the same memory - // (backed by a memfd). The difference between the two is that // zygote_mapping_methods_ is shared memory only usable by the zygote and not - // inherited by child processes. child_mapping_methods_ is a private mapping - // that all processes will map. + // inherited by child processes. We create it eagerly to ensure other + // processes cannot seal writable the file. MemMap zygote_mapping_methods_; - MemMap child_mapping_methods_; + + // The file descriptor created through memfd_create pointing to memory holding + // boot image methods. Created by the zygote, and inherited by child + // processes. The descriptor will be closed in each process (including the + // zygote) once they don't need it. + android::base::unique_fd fd_methods_; + + // The size of the memory pointed by `fd_methods_`. Cached here to avoid + // recomputing it. + size_t fd_methods_size_; DISALLOW_COPY_AND_ASSIGN(Jit); }; diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 6a13d59363..82ca44c2cd 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -1838,15 +1838,18 @@ void ZygoteMap::Initialize(uint32_t number_of_methods) { // Allocate for 40-80% capacity. This will offer OK lookup times, and termination // cases. size_t capacity = RoundUpToPowerOfTwo(number_of_methods * 100 / 80); - const Entry* data = - reinterpret_cast<const Entry*>(region_->AllocateData(capacity * sizeof(Entry))); - if (data != nullptr) { - region_->FillData(data, capacity, Entry { nullptr, nullptr }); - map_ = ArrayRef(data, capacity); + const uint8_t* memory = region_->AllocateData( + capacity * sizeof(Entry) + sizeof(ZygoteCompilationState)); + if (memory == nullptr) { + LOG(WARNING) << "Could not allocate data for the zygote map"; + return; } - done_ = reinterpret_cast<const bool*>(region_->AllocateData(sizeof(bool))); - CHECK(done_ != nullptr) << "Could not allocate a single boolean in the JIT region"; - region_->WriteData(done_, false); + const Entry* data = reinterpret_cast<const Entry*>(memory); + region_->FillData(data, capacity, Entry { nullptr, nullptr }); + map_ = ArrayRef(data, capacity); + compilation_state_ = reinterpret_cast<const ZygoteCompilationState*>( + memory + capacity * sizeof(Entry)); + region_->WriteData(compilation_state_, ZygoteCompilationState::kInProgress); } const void* ZygoteMap::GetCodeFor(ArtMethod* method, uintptr_t pc) const { diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index 12425cf467..58cf0e36a7 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -79,6 +79,22 @@ class MarkCodeClosure; // of garbage collecting code. using CodeCacheBitmap = gc::accounting::MemoryRangeBitmap<kJitCodeAccountingBytes>; +// The state of profile-based compilation in the zygote. +// - kInProgress: JIT compilation is happening +// - kDone: JIT compilation is finished, and the zygote is preparing notifying +// the other processes. +// - kNotifiedOk: the zygote has notified the other processes, which can start +// sharing the boot image method mappings. +// - kNotifiedFailure: the zygote has notified the other processes, but they +// cannot share the boot image method mappings due to +// unexpected errors +enum class ZygoteCompilationState : uint8_t { + kInProgress = 0, + kDone = 1, + kNotifiedOk = 2, + kNotifiedFailure = 3, +}; + // Class abstraction over a map of ArtMethod -> compiled code, where the // ArtMethod are compiled by the zygote, and the map acts as a communication // channel between the zygote and the other processes. @@ -88,7 +104,8 @@ using CodeCacheBitmap = gc::accounting::MemoryRangeBitmap<kJitCodeAccountingByte // This map is writable only by the zygote, and readable by all children. class ZygoteMap { public: - explicit ZygoteMap(JitMemoryRegion* region) : map_(), region_(region), done_(nullptr) {} + explicit ZygoteMap(JitMemoryRegion* region) + : map_(), region_(region), compilation_state_(nullptr) {} // Initialize the data structure so it can hold `number_of_methods` mappings. // Note that the map is fixed size and never grows. @@ -106,12 +123,21 @@ class ZygoteMap { return GetCodeFor(method) != nullptr; } - void SetCompilationDone() { - region_->WriteData(done_, true); + void SetCompilationState(ZygoteCompilationState state) { + region_->WriteData(compilation_state_, state); + } + + bool IsCompilationDoneButNotNotified() const { + return compilation_state_ != nullptr && *compilation_state_ == ZygoteCompilationState::kDone; + } + + bool IsCompilationNotified() const { + return compilation_state_ != nullptr && *compilation_state_ > ZygoteCompilationState::kDone; } - bool IsCompilationDone() const { - return *done_; + bool CanMapBootImageMethods() const { + return compilation_state_ != nullptr && + *compilation_state_ == ZygoteCompilationState::kNotifiedOk; } private: @@ -129,7 +155,9 @@ class ZygoteMap { // The region in which the map is allocated. JitMemoryRegion* const region_; - const bool* done_; + // The current state of compilation in the zygote. Starts with kInProgress, + // and should end with kNotifiedOk or kNotifiedFailure. + const ZygoteCompilationState* compilation_state_; DISALLOW_COPY_AND_ASSIGN(ZygoteMap); }; diff --git a/runtime/jit/jit_memory_region.cc b/runtime/jit/jit_memory_region.cc index 09980c8d36..7c7496b9e3 100644 --- a/runtime/jit/jit_memory_region.cc +++ b/runtime/jit/jit_memory_region.cc @@ -114,14 +114,12 @@ bool JitMemoryRegion::Initialize(size_t initial_capacity, // +---------------+ // | non exec code |\ // +---------------+ \ - // | writable data |\ \ - // +---------------+ \ \ - // : :\ \ \ - // +---------------+.\.\.+---------------+ - // | exec code | \ \| code | - // +---------------+...\.+---------------+ - // | readonly data | \| data | - // +---------------+.....+---------------+ + // : :\ \ + // +---------------+.\.+---------------+ + // | exec code | \| code | + // +---------------+...+---------------+ + // | data | | data | + // +---------------+...+---------------+ // // In this configuration code updates are written to the non-executable view of the code // cache, and the executable view of the code cache has fixed RX memory protections. @@ -134,7 +132,7 @@ bool JitMemoryRegion::Initialize(size_t initial_capacity, base_flags = MAP_SHARED; data_pages = MemMap::MapFile( data_capacity + exec_capacity, - kProtR, + is_zygote ? kProtR : kProtRW, base_flags, mem_fd, /* start= */ 0, @@ -216,34 +214,36 @@ bool JitMemoryRegion::Initialize(size_t initial_capacity, return false; } } - // Create a dual view of the data cache. - name = data_cache_name + "-rw"; - writable_data_pages = MemMap::MapFile(data_capacity, - kProtRW, - base_flags, - mem_fd, - /* start= */ 0, - /* low_4GB= */ false, - name.c_str(), - &error_str); - if (!writable_data_pages.IsValid()) { - std::ostringstream oss; - oss << "Failed to create dual data view: " << error_str; - *error_msg = oss.str(); - return false; - } - if (writable_data_pages.MadviseDontFork() != 0) { - *error_msg = "Failed to madvise dont fork the writable data view"; - return false; - } - if (non_exec_pages.MadviseDontFork() != 0) { - *error_msg = "Failed to madvise dont fork the writable code view"; - return false; - } - // Now that we have created the writable and executable mappings, prevent creating any new - // ones. - if (is_zygote && !ProtectZygoteMemory(mem_fd.get(), error_msg)) { - return false; + // For the zygote, create a dual view of the data cache. + if (is_zygote) { + name = data_cache_name + "-rw"; + writable_data_pages = MemMap::MapFile(data_capacity, + kProtRW, + base_flags, + mem_fd, + /* start= */ 0, + /* low_4GB= */ false, + name.c_str(), + &error_str); + if (!writable_data_pages.IsValid()) { + std::ostringstream oss; + oss << "Failed to create dual data view for zygote: " << error_str; + *error_msg = oss.str(); + return false; + } + if (writable_data_pages.MadviseDontFork() != 0) { + *error_msg = "Failed to madvise dont fork the writable data view"; + return false; + } + if (non_exec_pages.MadviseDontFork() != 0) { + *error_msg = "Failed to madvise dont fork the writable code view"; + return false; + } + // Now that we have created the writable and executable mappings, prevent creating any new + // ones. + if (!ProtectZygoteMemory(mem_fd.get(), error_msg)) { + return false; + } } } } else { diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc index 38bd611c38..2dc9f672dd 100644 --- a/runtime/method_handles.cc +++ b/runtime/method_handles.cc @@ -419,6 +419,7 @@ static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type) static inline bool MethodHandleInvokeMethod(ArtMethod* called_method, Handle<mirror::MethodType> callsite_type, Handle<mirror::MethodType> target_type, + Handle<mirror::MethodType> nominal_type, Thread* self, ShadowFrame& shadow_frame, const InstructionOperands* const operands, @@ -543,6 +544,11 @@ static inline bool MethodHandleInvokeMethod(ArtMethod* called_method, return false; } + if (nominal_type != nullptr) { + return ConvertReturnValue(nominal_type, target_type, result) && + ConvertReturnValue(callsite_type, nominal_type, result); + } + return ConvertReturnValue(callsite_type, target_type, result); } @@ -714,8 +720,9 @@ bool DoInvokePolymorphicMethod(Thread* self, const InstructionOperands* const operands, JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) { - StackHandleScope<1> hs(self); + StackHandleScope<2> hs(self); Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType())); + Handle<mirror::MethodType> nominal_handle_type(hs.NewHandle(method_handle->GetNominalType())); const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind(); DCHECK(IsInvoke(handle_kind)); @@ -761,6 +768,7 @@ bool DoInvokePolymorphicMethod(Thread* self, return MethodHandleInvokeMethod(called_method, callsite_type, handle_type, + nominal_handle_type, self, shadow_frame, operands, diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index a0e8a237c5..e1dd54f6f7 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -1040,6 +1040,24 @@ void Class::ClearSkipAccessChecksFlagOnAllMethods(PointerSize pointer_size) { } } +void Class::ClearMustCountLocksFlagOnAllMethods(PointerSize pointer_size) { + DCHECK(IsVerified()); + for (auto& m : GetMethods(pointer_size)) { + if (!m.IsNative() && m.IsInvokable()) { + m.ClearMustCountLocks(); + } + } +} + +void Class::ClearDontCompileFlagOnAllMethods(PointerSize pointer_size) { + DCHECK(IsVerified()); + for (auto& m : GetMethods(pointer_size)) { + if (!m.IsNative() && m.IsInvokable()) { + m.ClearDontCompile(); + } + } +} + void Class::SetSkipAccessChecksFlagOnAllMethods(PointerSize pointer_size) { DCHECK(IsVerified()); for (auto& m : GetMethods(pointer_size)) { diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index d925a96d9c..15f7dc6177 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -1134,6 +1134,13 @@ class MANAGED Class final : public Object { static ObjPtr<mirror::Class> GetPrimitiveClass(ObjPtr<mirror::String> name) REQUIRES_SHARED(Locks::mutator_lock_); + // Clear the kAccMustCountLocks flag on each method, for class redefinition. + void ClearMustCountLocksFlagOnAllMethods(PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_); + // Clear the kAccCompileDontBother flag on each method, for class redefinition. + void ClearDontCompileFlagOnAllMethods(PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_); + // Clear the kAccSkipAccessChecks flag on each method, for class redefinition. void ClearSkipAccessChecksFlagOnAllMethods(PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc index c2c47a5187..96fc403690 100644 --- a/runtime/mirror/dex_cache.cc +++ b/runtime/mirror/dex_cache.cc @@ -176,19 +176,33 @@ void DexCache::InitializeDexCache(Thread* self, void DexCache::VisitReflectiveTargets(ReflectiveValueVisitor* visitor) { for (size_t i = 0; i < NumResolvedFields(); i++) { auto pair(GetNativePairPtrSize(GetResolvedFields(), i, kRuntimePointerSize)); + if (pair.index == FieldDexCachePair::InvalidIndexForSlot(i)) { + continue; + } ArtField* new_val = visitor->VisitField( pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedField, pair.index, this)); if (UNLIKELY(new_val != pair.object)) { - pair.object = new_val; + if (new_val == nullptr) { + pair = FieldDexCachePair(nullptr, FieldDexCachePair::InvalidIndexForSlot(i)); + } else { + pair.object = new_val; + } SetNativePairPtrSize(GetResolvedFields(), i, pair, kRuntimePointerSize); } } for (size_t i = 0; i < NumResolvedMethods(); i++) { auto pair(GetNativePairPtrSize(GetResolvedMethods(), i, kRuntimePointerSize)); + if (pair.index == MethodDexCachePair::InvalidIndexForSlot(i)) { + continue; + } ArtMethod* new_val = visitor->VisitMethod( pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedMethod, pair.index, this)); if (UNLIKELY(new_val != pair.object)) { - pair.object = new_val; + if (new_val == nullptr) { + pair = MethodDexCachePair(nullptr, MethodDexCachePair::InvalidIndexForSlot(i)); + } else { + pair.object = new_val; + } SetNativePairPtrSize(GetResolvedMethods(), i, pair, kRuntimePointerSize); } } diff --git a/runtime/mirror/method_handle_impl-inl.h b/runtime/mirror/method_handle_impl-inl.h index 932b4343f3..27ccc53e83 100644 --- a/runtime/mirror/method_handle_impl-inl.h +++ b/runtime/mirror/method_handle_impl-inl.h @@ -33,12 +33,6 @@ inline ObjPtr<mirror::MethodType> MethodHandle::GetNominalType() { return GetFieldObject<mirror::MethodType>(OFFSET_OF_OBJECT_MEMBER(MethodHandle, nominal_type_)); } -inline ObjPtr<mirror::Class> MethodHandle::GetTargetClass() { - Kind kind = GetHandleKind(); - return (kind < kFirstAccessorKind) ? - GetTargetMethod()->GetDeclaringClass() : GetTargetField()->GetDeclaringClass(); -} - } // namespace mirror } // namespace art diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h index 54aa0c9bc2..a0f02f68c9 100644 --- a/runtime/mirror/method_handle_impl.h +++ b/runtime/mirror/method_handle_impl.h @@ -82,8 +82,6 @@ class MANAGED MethodHandle : public Object { GetField64(OFFSET_OF_OBJECT_MEMBER(MethodHandle, art_field_or_method_))); } - ALWAYS_INLINE ObjPtr<mirror::Class> GetTargetClass() REQUIRES_SHARED(Locks::mutator_lock_); - // Gets the return type for a named invoke method, or nullptr if the invoke method is not // supported. static const char* GetReturnTypeDescriptor(const char* invoke_method_name); diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 156895d03b..ce942c846d 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -134,24 +134,25 @@ static void CollectNonDebuggableClasses() REQUIRES(!Locks::mutator_lock_) { // Must match values in com.android.internal.os.Zygote. enum { - DEBUG_ENABLE_JDWP = 1, - DEBUG_ENABLE_CHECKJNI = 1 << 1, - DEBUG_ENABLE_ASSERT = 1 << 2, - DEBUG_ENABLE_SAFEMODE = 1 << 3, - DEBUG_ENABLE_JNI_LOGGING = 1 << 4, - DEBUG_GENERATE_DEBUG_INFO = 1 << 5, - DEBUG_ALWAYS_JIT = 1 << 6, - DEBUG_NATIVE_DEBUGGABLE = 1 << 7, - DEBUG_JAVA_DEBUGGABLE = 1 << 8, - DISABLE_VERIFIER = 1 << 9, - ONLY_USE_SYSTEM_OAT_FILES = 1 << 10, - DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11, - HIDDEN_API_ENFORCEMENT_POLICY_MASK = (1 << 12) - | (1 << 13), - PROFILE_SYSTEM_SERVER = 1 << 14, - PROFILE_FROM_SHELL = 1 << 15, - USE_APP_IMAGE_STARTUP_CACHE = 1 << 16, - DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17, + DEBUG_ENABLE_JDWP = 1, + DEBUG_ENABLE_CHECKJNI = 1 << 1, + DEBUG_ENABLE_ASSERT = 1 << 2, + DEBUG_ENABLE_SAFEMODE = 1 << 3, + DEBUG_ENABLE_JNI_LOGGING = 1 << 4, + DEBUG_GENERATE_DEBUG_INFO = 1 << 5, + DEBUG_ALWAYS_JIT = 1 << 6, + DEBUG_NATIVE_DEBUGGABLE = 1 << 7, + DEBUG_JAVA_DEBUGGABLE = 1 << 8, + DISABLE_VERIFIER = 1 << 9, + ONLY_USE_SYSTEM_OAT_FILES = 1 << 10, + DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11, + HIDDEN_API_ENFORCEMENT_POLICY_MASK = (1 << 12) + | (1 << 13), + PROFILE_SYSTEM_SERVER = 1 << 14, + PROFILE_FROM_SHELL = 1 << 15, + USE_APP_IMAGE_STARTUP_CACHE = 1 << 16, + DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17, + DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18, // bits to shift (flags & HIDDEN_API_ENFORCEMENT_POLICY_MASK) by to get a value // corresponding to hiddenapi::EnforcementPolicy @@ -319,6 +320,13 @@ static void ZygoteHooks_nativePostForkChild(JNIEnv* env, (runtime_flags & HIDDEN_API_ENFORCEMENT_POLICY_MASK) >> API_ENFORCEMENT_POLICY_SHIFT); runtime_flags &= ~HIDDEN_API_ENFORCEMENT_POLICY_MASK; + if ((runtime_flags & DISABLE_TEST_API_ENFORCEMENT_POLICY) != 0u) { + runtime->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDisabled); + } else { + runtime->SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled); + } + runtime_flags &= ~DISABLE_TEST_API_ENFORCEMENT_POLICY; + bool profile_system_server = (runtime_flags & PROFILE_SYSTEM_SERVER) == PROFILE_SYSTEM_SERVER; runtime_flags &= ~PROFILE_SYSTEM_SERVER; diff --git a/runtime/oat.cc b/runtime/oat.cc index db6cda5027..3fceec9364 100644 --- a/runtime/oat.cc +++ b/runtime/oat.cc @@ -411,9 +411,4 @@ void OatHeader::Flatten(const SafeMap<std::string, std::string>* key_value_store key_value_store_size_ = data_ptr - reinterpret_cast<char*>(&key_value_store_); } -OatMethodOffsets::OatMethodOffsets(uint32_t code_offset) : code_offset_(code_offset) { -} - -OatMethodOffsets::~OatMethodOffsets() {} - } // namespace art diff --git a/runtime/oat.h b/runtime/oat.h index 54d111cc31..352b9e892e 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,8 +32,8 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } }; - // Last oat version changed reason: Optimize stack map decoding - interleave varints. - static constexpr std::array<uint8_t, 4> kOatVersion { { '1', '7', '3', '\0' } }; + // Last oat version changed reason: Revert^4 Boot image extension. + static constexpr std::array<uint8_t, 4> kOatVersion { { '1', '7', '6', '\0' } }; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; static constexpr const char* kDebuggableKey = "debuggable"; @@ -134,31 +134,6 @@ class PACKED(4) OatHeader { DISALLOW_COPY_AND_ASSIGN(OatHeader); }; -// OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can -// save even one OatMethodOffsets struct, the more complicated encoding -// using a bitmap pays for itself since few classes will have 160 -// methods. -enum OatClassType { - kOatClassAllCompiled = 0, // OatClass is followed by an OatMethodOffsets for each method. - kOatClassSomeCompiled = 1, // A bitmap of which OatMethodOffsets are present follows the OatClass. - kOatClassNoneCompiled = 2, // All methods are interpreted so no OatMethodOffsets are necessary. - kOatClassMax = 3, -}; - -std::ostream& operator<<(std::ostream& os, const OatClassType& rhs); - -class PACKED(4) OatMethodOffsets { - public: - explicit OatMethodOffsets(uint32_t code_offset = 0); - - ~OatMethodOffsets(); - - OatMethodOffsets(const OatMethodOffsets&) = default; - OatMethodOffsets& operator=(const OatMethodOffsets&) = default; - - uint32_t code_offset_; -}; - } // namespace art #endif // ART_RUNTIME_OAT_H_ diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 4b5d5c328b..9ef5fbbbd3 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -855,14 +855,18 @@ bool OatFileBase::Setup(int zip_fd, const char* abs_dex_location, std::string* e CheckedCall(mprotect, "protect relocations", reloc_begin, DataBimgRelRoSize(), PROT_READ); // Make sure the file lists a boot image dependency, otherwise the .data.bimg.rel.ro // section is bogus. The full dependency is checked before the code is executed. - const char* boot_class_path_checksum = - GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); - if (boot_class_path_checksum == nullptr || - boot_class_path_checksum[0] != gc::space::ImageSpace::kImageChecksumPrefix) { - *error_msg = StringPrintf("Oat file '%s' contains .data.bimg.rel.ro section " - "without boot image dependency.", - GetLocation().c_str()); - return false; + // We cannot do this check if we do not have a key-value store, i.e. for secondary + // oat files for boot image extensions. + if (GetOatHeader().GetKeyValueStoreSize() != 0u) { + const char* boot_class_path_checksum = + GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); + if (boot_class_path_checksum == nullptr || + boot_class_path_checksum[0] != gc::space::ImageSpace::kImageChecksumPrefix) { + *error_msg = StringPrintf("Oat file '%s' contains .data.bimg.rel.ro section " + "without boot image dependency.", + GetLocation().c_str()); + return false; + } } } diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 69d5efd4f4..70a9534824 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -34,7 +34,6 @@ #include "dex/utf.h" #include "index_bss_mapping.h" #include "mirror/object.h" -#include "oat.h" #include "runtime.h" namespace art { @@ -61,6 +60,31 @@ class DummyOatFile; } // namespace collector } // namespace gc +// OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can +// save even one OatMethodOffsets struct, the more complicated encoding +// using a bitmap pays for itself since few classes will have 160 +// methods. +enum OatClassType { + kOatClassAllCompiled = 0, // OatClass is followed by an OatMethodOffsets for each method. + kOatClassSomeCompiled = 1, // A bitmap of OatMethodOffsets that are present follows the OatClass. + kOatClassNoneCompiled = 2, // All methods are interpreted so no OatMethodOffsets are necessary. + kOatClassMax = 3, +}; + +std::ostream& operator<<(std::ostream& os, const OatClassType& rhs); + +class PACKED(4) OatMethodOffsets { + public: + explicit OatMethodOffsets(uint32_t code_offset = 0) : code_offset_(code_offset) {} + + ~OatMethodOffsets() {} + + OatMethodOffsets(const OatMethodOffsets&) = default; + OatMethodOffsets& operator=(const OatMethodOffsets&) = default; + + uint32_t code_offset_; +}; + // Runtime representation of the OAT file format which holds compiler output. // The class opens an OAT file from storage and maps it to memory, typically with // dlopen and provides access to its internal data structures (see OatWriter for diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index ab1389af56..48f17f01f0 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -604,78 +604,55 @@ const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { } bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file) { - // Get the BCP from the oat file. + // Get the checksums and the BCP from the oat file. + const char* oat_boot_class_path_checksums = + oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); const char* oat_boot_class_path = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathKey); - if (oat_boot_class_path == nullptr) { + if (oat_boot_class_path_checksums == nullptr || oat_boot_class_path == nullptr) { return false; } - - // Check that the oat BCP is a prefix of current BCP locations and count components. - Runtime* runtime = Runtime::Current(); - size_t component_count = 0u; - std::string_view remaining_bcp(oat_boot_class_path); - bool bcp_ok = false; - for (const std::string& location : runtime->GetBootClassPathLocations()) { - if (!StartsWith(remaining_bcp, location)) { - break; - } - remaining_bcp.remove_prefix(location.size()); - ++component_count; - if (remaining_bcp.empty()) { - bcp_ok = true; - break; - } - if (!StartsWith(remaining_bcp, ":")) { - break; - } - remaining_bcp.remove_prefix(1u); - } - if (!bcp_ok) { - return false; - } - - // Get the checksums. - const char* oat_boot_class_path_checksums = - oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); - if (oat_boot_class_path_checksums == nullptr) { - return false; + std::string_view oat_boot_class_path_checksums_view(oat_boot_class_path_checksums); + std::string_view oat_boot_class_path_view(oat_boot_class_path); + if (oat_boot_class_path_view == cached_boot_class_path_ && + oat_boot_class_path_checksums_view == cached_boot_class_path_checksums_) { + return true; } - // Retrieve checksums for this portion of the BCP if we do not have them cached. - if (cached_boot_class_path_checksum_component_count_ != component_count) { - ArrayRef<const std::string> boot_class_path(runtime->GetBootClassPath()); - std::string error_msg; - std::string boot_class_path_checksums = gc::space::ImageSpace::GetBootClassPathChecksums( - boot_class_path.SubArray(/* pos= */ 0u, component_count), - runtime->GetImageLocation(), - isa_, - runtime->GetImageSpaceLoadingOrder(), - &error_msg); - if (boot_class_path_checksums.empty()) { - VLOG(oat) << "No image for oat image checksum to match against."; - - if (HasOriginalDexFiles()) { - return false; - } - - // If there is no original dex file to fall back to, grudgingly accept - // the oat file. This could technically lead to crashes, but there's no - // way we could find a better oat file to use for this dex location, - // and it's better than being stuck in a boot loop with no way out. - // The problem will hopefully resolve itself the next time the runtime - // starts up. - LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. " - << "Allow oat file use. This is potentially dangerous."; + Runtime* runtime = Runtime::Current(); + std::string error_msg; + bool result = gc::space::ImageSpace::VerifyBootClassPathChecksums( + oat_boot_class_path_checksums_view, + oat_boot_class_path_view, + runtime->GetImageLocation(), + ArrayRef<const std::string>(runtime->GetBootClassPathLocations()), + ArrayRef<const std::string>(runtime->GetBootClassPath()), + isa_, + runtime->GetImageSpaceLoadingOrder(), + &error_msg); + if (!result) { + VLOG(oat) << "Failed to verify checksums of oat file " << oat_file.GetLocation() + << " error: " << error_msg; - return true; + if (HasOriginalDexFiles()) { + return false; } - cached_boot_class_path_checksum_component_count_ = component_count; - cached_boot_class_path_checksums_ = boot_class_path_checksums; + + // If there is no original dex file to fall back to, grudgingly accept + // the oat file. This could technically lead to crashes, but there's no + // way we could find a better oat file to use for this dex location, + // and it's better than being stuck in a boot loop with no way out. + // The problem will hopefully resolve itself the next time the runtime + // starts up. + LOG(WARNING) << "Dex location " << dex_location_ << " does not seem to include dex file. " + << "Allow oat file use. This is potentially dangerous."; + return true; } - // Compare the checksums. - return cached_boot_class_path_checksums_ == oat_boot_class_path_checksums; + // This checksum has been validated, so save it. + cached_boot_class_path_ = oat_boot_class_path_view; + cached_boot_class_path_checksums_ = oat_boot_class_path_checksums_view; + return true; } OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 1f3f74f611..b5812f9e25 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -439,7 +439,7 @@ class OatFileAssistant { // File descriptor corresponding to apk, dex file, or zip. int zip_fd_; - size_t cached_boot_class_path_checksum_component_count_ = 0u; + std::string cached_boot_class_path_; std::string cached_boot_class_path_checksums_; friend class OatFileAssistantTest; diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 9a5409f46d..0412ab5909 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -34,6 +34,7 @@ #include "common_runtime_test.h" #include "dexopt_test.h" #include "hidden_api.h" +#include "oat.h" #include "oat_file.h" #include "oat_file_manager.h" #include "scoped_thread_state_change-inl.h" diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index e4c7dc3081..687ec544e3 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -473,7 +473,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( OatFileAssistant oat_file_assistant(dex_location, kRuntimeISA, - !runtime->IsAotCompiler(), + runtime->GetOatFilesExecutable(), only_use_system_oat_files_); // Get the oat file on disk. diff --git a/runtime/reflective_value_visitor.h b/runtime/reflective_value_visitor.h index 0b09a0bf8a..3a72760345 100644 --- a/runtime/reflective_value_visitor.h +++ b/runtime/reflective_value_visitor.h @@ -109,6 +109,10 @@ class ReflectionSourceInfo : public ValueObject { os << "Type=" << type_; } + ReflectionSourceType GetType() const { + return type_; + } + private: const ReflectionSourceType type_; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 597cd6bd2a..6d17204ef5 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -146,6 +146,7 @@ #include "native_bridge_art_interface.h" #include "native_stack_dump.h" #include "nativehelper/scoped_local_ref.h" +#include "oat.h" #include "oat_file.h" #include "oat_file_manager.h" #include "object_callbacks.h" @@ -288,6 +289,7 @@ Runtime::Runtime() safe_mode_(false), hidden_api_policy_(hiddenapi::EnforcementPolicy::kDisabled), core_platform_api_policy_(hiddenapi::EnforcementPolicy::kDisabled), + test_api_policy_(hiddenapi::EnforcementPolicy::kDisabled), dedupe_hidden_api_warnings_(true), hidden_api_access_event_log_rate_(0), dump_native_stack_on_sig_quit_(true), @@ -986,7 +988,6 @@ void Runtime::InitNonZygoteOrPostFork( UnloadNativeBridge(); is_native_bridge_loaded_ = false; break; - case NativeBridgeAction::kInitialize: InitializeNativeBridge(env, isa); break; @@ -1861,6 +1862,16 @@ void Runtime::InitNativeMethods() { // a regular JNI libraries with a regular JNI_OnLoad. Most JNI libraries can // just use System.loadLibrary, but libcore can't because it's the library // that implements System.loadLibrary! + + // libicu_jni has to be initialized before libopenjdk{d} due to runtime dependency from + // libopenjdk{d} to Icu4cMetadata native methods in libicu_jni. See http://b/143888405 + { + std::string error_msg; + if (!java_vm_->LoadNativeLibrary( + env, "libicu_jni.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) { + LOG(FATAL) << "LoadNativeLibrary failed for \"libicu_jni.so\": " << error_msg; + } + } { std::string error_msg; if (!java_vm_->LoadNativeLibrary( @@ -1878,13 +1889,6 @@ void Runtime::InitNativeMethods() { LOG(FATAL) << "LoadNativeLibrary failed for \"" << kOpenJdkLibrary << "\": " << error_msg; } } - { - std::string error_msg; - if (!java_vm_->LoadNativeLibrary( - env, "libicu_jni.so", nullptr, WellKnownClasses::java_lang_Object, &error_msg)) { - LOG(FATAL) << "LoadNativeLibrary failed for \"libicu_jni.so\": " << error_msg; - } - } // Initialize well known classes that may invoke runtime native methods. WellKnownClasses::LateInit(env); @@ -2231,7 +2235,8 @@ void Runtime::VisitImageRoots(RootVisitor* visitor) { } } -static ArtMethod* CreateRuntimeMethod(ClassLinker* class_linker, LinearAlloc* linear_alloc) { +static ArtMethod* CreateRuntimeMethod(ClassLinker* class_linker, LinearAlloc* linear_alloc) + REQUIRES_SHARED(Locks::mutator_lock_) { const PointerSize image_pointer_size = class_linker->GetImagePointerSize(); const size_t method_alignment = ArtMethod::Alignment(image_pointer_size); const size_t method_size = ArtMethod::Size(image_pointer_size); @@ -2964,4 +2969,8 @@ void Runtime::SetJniIdType(JniIdType t) { WellKnownClasses::HandleJniIdTypeChange(Thread::Current()->GetJniEnv()); } +bool Runtime::GetOatFilesExecutable() const { + return !IsAotCompiler() && !(IsSystemServer() && jit_options_->GetSaveProfilingInfo()); +} + } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index 4975e6588b..cfa67a81de 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -435,7 +435,7 @@ class Runtime { imt_conflict_method_ = nullptr; } - void FixupConflictTables(); + void FixupConflictTables() REQUIRES_SHARED(Locks::mutator_lock_); void SetImtConflictMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); void SetImtUnimplementedMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_); @@ -601,6 +601,14 @@ class Runtime { return core_platform_api_policy_; } + void SetTestApiEnforcementPolicy(hiddenapi::EnforcementPolicy policy) { + test_api_policy_ = policy; + } + + hiddenapi::EnforcementPolicy GetTestApiEnforcementPolicy() const { + return test_api_policy_; + } + void SetHiddenApiExemptions(const std::vector<std::string>& exemptions) { hidden_api_exemptions_ = exemptions; } @@ -942,6 +950,9 @@ class Runtime { return verifier_missing_kthrow_fatal_; } + // Return true if we should load oat files as executable or not. + bool GetOatFilesExecutable() const; + private: static void InitPlatformSignalHandlers(); @@ -1215,6 +1226,9 @@ class Runtime { // Whether access checks on core platform API should be performed. hiddenapi::EnforcementPolicy core_platform_api_policy_; + // Whether access checks on test API should be performed. + hiddenapi::EnforcementPolicy test_api_policy_; + // List of signature prefixes of methods that have been removed from the blacklist, and treated // as if whitelisted. std::vector<std::string> hidden_api_exemptions_; diff --git a/runtime/thread.cc b/runtime/thread.cc index 59a38e161d..36c35f85ba 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2439,13 +2439,19 @@ void Thread::Destroy() { { ScopedObjectAccess soa(self); Runtime::Current()->GetHeap()->RevokeThreadLocalBuffers(this); - if (kUseReadBarrier) { - Runtime::Current()->GetHeap()->ConcurrentCopyingCollector()->RevokeThreadLocalMarkStack(this); - } } } Thread::~Thread() { + if (kUseReadBarrier) { + // It's a cheap operation so can be done in the destructor (instead of + // Destroy()). + // Doing it without mutator_lock mutual exclusion is also necessary as there + // is a checkpoint in ConcurrentCopying which scans the threads' stacks, + // thereby assigning a thread-local mark-stack to self, breaking some + // assumptions about how the GC works (see b/140119552). + Runtime::Current()->GetHeap()->ConcurrentCopyingCollector()->RevokeThreadLocalMarkStack(this); + } CHECK(tlsPtr_.class_loader_override == nullptr); CHECK(tlsPtr_.jpeer == nullptr); CHECK(tlsPtr_.opeer == nullptr); diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index e49092e0ec..e01d21ebe1 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -331,9 +331,16 @@ ArrayRef<const uint8_t> VdexFile::GetQuickenedInfoOf(const DexFile& dex_file, static std::string ComputeBootClassPathChecksumString() { Runtime* const runtime = Runtime::Current(); + // Do not include boot image extension checksums, use their dex file checksums instead. Unlike + // oat files, vdex files do not reference anything in image spaces, so there is no reason why + // loading or not loading a boot image extension would affect the validity of the vdex file. + // Note: Update of a boot class path module such as conscrypt invalidates the vdex file anyway. + ArrayRef<gc::space::ImageSpace* const> image_spaces(runtime->GetHeap()->GetBootImageSpaces()); + size_t boot_image_components = + image_spaces.empty() ? 0u : image_spaces[0]->GetImageHeader().GetComponentCount(); return gc::space::ImageSpace::GetBootClassPathChecksums( - runtime->GetHeap()->GetBootImageSpaces(), - runtime->GetClassLinker()->GetBootClassPath()); + image_spaces.SubArray(/*pos=*/ 0u, boot_image_components), + ArrayRef<const DexFile* const>(runtime->GetClassLinker()->GetBootClassPath())); } static bool CreateDirectories(const std::string& child_path, /* out */ std::string* error_msg) { diff --git a/runtime/verifier/class_verifier.cc b/runtime/verifier/class_verifier.cc index fca9e16450..ed83652c8f 100644 --- a/runtime/verifier/class_verifier.cc +++ b/runtime/verifier/class_verifier.cc @@ -21,6 +21,8 @@ #include "art_method-inl.h" #include "base/enums.h" +#include "base/locks.h" +#include "base/logging.h" #include "base/systrace.h" #include "base/utils.h" #include "class_linker.h" @@ -36,6 +38,8 @@ #include "mirror/dex_cache.h" #include "runtime.h" #include "thread.h" +#include "verifier/method_verifier.h" +#include "verifier/reg_type_cache.h" namespace art { namespace verifier { @@ -46,6 +50,20 @@ using android::base::StringPrintf; // sure we only print this once. static bool gPrintedDxMonitorText = false; +class StandardVerifyCallback : public VerifierCallback { + public: + void SetDontCompile(ArtMethod* m, bool value) override REQUIRES_SHARED(Locks::mutator_lock_) { + if (value) { + m->SetDontCompile(); + } + } + void SetMustCountLocks(ArtMethod* m, bool value) override REQUIRES_SHARED(Locks::mutator_lock_) { + if (value) { + m->SetMustCountLocks(); + } + } +}; + FailureKind ClassVerifier::ReverifyClass(Thread* self, ObjPtr<mirror::Class> klass, HardFailLogMode log_level, @@ -54,19 +72,58 @@ FailureKind ClassVerifier::ReverifyClass(Thread* self, DCHECK(!Runtime::Current()->IsAotCompiler()); StackHandleScope<1> hs(self); Handle<mirror::Class> h_klass(hs.NewHandle(klass)); - ScopedAssertNoThreadSuspension sants(__FUNCTION__); + // We don't want to mess with these while other mutators are possibly looking at them. Instead we + // will wait until we can update them while everything is suspended. + class DelayedVerifyCallback : public VerifierCallback { + public: + void SetDontCompile(ArtMethod* m, bool value) override REQUIRES_SHARED(Locks::mutator_lock_) { + dont_compiles_.push_back({ m, value }); + } + void SetMustCountLocks(ArtMethod* m, bool value) override + REQUIRES_SHARED(Locks::mutator_lock_) { + count_locks_.push_back({ m, value }); + } + void UpdateFlags(bool skip_access_checks) REQUIRES(Locks::mutator_lock_) { + for (auto it : count_locks_) { + VLOG(verifier_debug) << "Setting " << it.first->PrettyMethod() << " count locks to " + << it.second; + if (it.second) { + it.first->SetMustCountLocks(); + } else { + it.first->ClearMustCountLocks(); + } + if (skip_access_checks && it.first->IsInvokable() && !it.first->IsNative()) { + it.first->SetSkipAccessChecks(); + } + } + for (auto it : dont_compiles_) { + VLOG(verifier_debug) << "Setting " << it.first->PrettyMethod() << " dont-compile to " + << it.second; + if (it.second) { + it.first->SetDontCompile(); + } else { + it.first->ClearDontCompile(); + } + } + } + + private: + std::vector<std::pair<ArtMethod*, bool>> dont_compiles_; + std::vector<std::pair<ArtMethod*, bool>> count_locks_; + }; + DelayedVerifyCallback dvc; FailureKind res = CommonVerifyClass(self, h_klass.Get(), /*callbacks=*/nullptr, + &dvc, /*allow_soft_failures=*/false, log_level, api_level, - /*can_allocate=*/ false, error); - if (res == FailureKind::kSoftFailure) { - // We cannot skip access checks since there was a soft failure. - h_klass->ClearSkipAccessChecksFlagOnAllMethods(kRuntimePointerSize); - } + DCHECK_NE(res, FailureKind::kHardFailure); + ScopedThreadSuspension sts(Thread::Current(), ThreadState::kSuspended); + ScopedSuspendAll ssa("Update method flags for reverify"); + dvc.UpdateFlags(res == FailureKind::kNoFailure); return res; } @@ -80,22 +137,24 @@ FailureKind ClassVerifier::VerifyClass(Thread* self, if (klass->IsVerified()) { return FailureKind::kNoFailure; } + StandardVerifyCallback svc; return CommonVerifyClass(self, klass, callbacks, + &svc, allow_soft_failures, log_level, api_level, - /*can_allocate=*/ true, error); } + FailureKind ClassVerifier::CommonVerifyClass(Thread* self, ObjPtr<mirror::Class> klass, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, uint32_t api_level, - bool can_allocate, std::string* error) { bool early_failure = false; std::string failure_message; @@ -130,13 +189,14 @@ FailureKind ClassVerifier::CommonVerifyClass(Thread* self, class_loader, *class_def, callbacks, + verifier_callback, allow_soft_failures, log_level, api_level, - can_allocate, error); } + FailureKind ClassVerifier::VerifyClass(Thread* self, const DexFile* dex_file, Handle<mirror::DexCache> dex_cache, @@ -147,16 +207,17 @@ FailureKind ClassVerifier::VerifyClass(Thread* self, HardFailLogMode log_level, uint32_t api_level, std::string* error) { + StandardVerifyCallback svc; return VerifyClass(self, dex_file, dex_cache, class_loader, class_def, callbacks, + &svc, allow_soft_failures, log_level, api_level, - /*can_allocate=*/ true, error); } @@ -166,10 +227,10 @@ FailureKind ClassVerifier::VerifyClass(Thread* self, Handle<mirror::ClassLoader> class_loader, const dex::ClassDef& class_def, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, uint32_t api_level, - bool can_allocate, std::string* error) { // A class must not be abstract and final. if ((class_def.access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) { @@ -220,12 +281,12 @@ FailureKind ClassVerifier::VerifyClass(Thread* self, resolved_method, method.GetAccessFlags(), callbacks, + verifier_callback, allow_soft_failures, log_level, /*need_precise_constants=*/ false, api_level, Runtime::Current()->IsAotCompiler(), - can_allocate, &hard_failure_msg); if (result.kind == FailureKind::kHardFailure) { if (failure_data.kind == FailureKind::kHardFailure) { diff --git a/runtime/verifier/class_verifier.h b/runtime/verifier/class_verifier.h index c97ea24799..0b229662e8 100644 --- a/runtime/verifier/class_verifier.h +++ b/runtime/verifier/class_verifier.h @@ -25,6 +25,8 @@ #include "base/locks.h" #include "handle.h" #include "obj_ptr.h" +#include "verifier/method_verifier.h" +#include "verifier/reg_type_cache.h" #include "verifier_enums.h" namespace art { @@ -50,14 +52,16 @@ namespace verifier { // Verifier that ensures the complete class is OK. class ClassVerifier { public: - // Redo verification on a loaded class. This is for use by class redefinition. Since the class is - // already loaded and in use this can only be performed with the mutator lock held. + // Redo verification on a loaded class. This is for use by class redefinition. This must be called + // with all methods already having all of kAccDontCompile and kAccCountLocks and not having + // kAccSkipAccessChecks. This will remove some of these flags from the method. The caller must + // ensure this cannot race with other changes to the verification class flags. static FailureKind ReverifyClass(Thread* self, ObjPtr<mirror::Class> klass, HardFailLogMode log_level, uint32_t api_level, std::string* error) - REQUIRES(Locks::mutator_lock_); + REQUIRES_SHARED(Locks::mutator_lock_); // Verify a class. Returns "kNoFailure" on success. static FailureKind VerifyClass(Thread* self, ObjPtr<mirror::Class> klass, @@ -78,18 +82,6 @@ class ClassVerifier { uint32_t api_level, std::string* error) REQUIRES_SHARED(Locks::mutator_lock_); - static FailureKind VerifyClass(Thread* self, - const DexFile* dex_file, - Handle<mirror::DexCache> dex_cache, - Handle<mirror::ClassLoader> class_loader, - const dex::ClassDef& class_def, - CompilerCallbacks* callbacks, - bool allow_soft_failures, - HardFailLogMode log_level, - uint32_t api_level, - bool can_allocate, - std::string* error) - REQUIRES_SHARED(Locks::mutator_lock_); static void Init(ClassLinker* class_linker) REQUIRES_SHARED(Locks::mutator_lock_); static void Shutdown(); @@ -101,13 +93,25 @@ class ClassVerifier { static FailureKind CommonVerifyClass(Thread* self, ObjPtr<mirror::Class> klass, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, uint32_t api_level, - bool can_allocate, std::string* error) REQUIRES_SHARED(Locks::mutator_lock_); + static FailureKind VerifyClass(Thread* self, + const DexFile* dex_file, + Handle<mirror::DexCache> dex_cache, + Handle<mirror::ClassLoader> class_loader, + const dex::ClassDef& class_def, + CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, + bool allow_soft_failures, + HardFailLogMode log_level, + uint32_t api_level, + std::string* error) + REQUIRES_SHARED(Locks::mutator_lock_); DISALLOW_COPY_AND_ASSIGN(ClassVerifier); }; diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 29bc40c30c..742f50dec8 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -5107,12 +5107,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, ArtMethod* method, uint32_t method_access_flags, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, uint32_t api_level, bool aot_mode, - bool allow_suspension, std::string* hard_failure_msg) { if (VLOG_IS_ON(verifier_debug)) { return VerifyMethod<true>(self, @@ -5127,12 +5127,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, method, method_access_flags, callbacks, + verifier_callback, allow_soft_failures, log_level, need_precise_constants, api_level, aot_mode, - allow_suspension, hard_failure_msg); } else { return VerifyMethod<false>(self, @@ -5147,12 +5147,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, method, method_access_flags, callbacks, + verifier_callback, allow_soft_failures, log_level, need_precise_constants, api_level, aot_mode, - allow_suspension, hard_failure_msg); } } @@ -5170,12 +5170,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, ArtMethod* method, uint32_t method_access_flags, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, uint32_t api_level, bool aot_mode, - bool allow_suspension, std::string* hard_failure_msg) { MethodVerifier::FailureData result; uint64_t start_ns = kTimeVerifyMethod ? NanoTime() : 0; @@ -5186,8 +5186,8 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, dex_file, code_item, method_idx, - /* can_load_classes= */ allow_suspension, - /* allow_thread_suspension= */ allow_suspension, + /* can_load_classes= */ true, + /* allow_thread_suspension= */ true, allow_soft_failures, aot_mode, dex_cache, @@ -5209,6 +5209,7 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, callbacks->MethodVerified(&verifier); } + bool set_dont_compile = false; if (verifier.failures_.size() != 0) { if (VLOG_IS_ON(verifier)) { verifier.DumpFailures(VLOG_STREAM(verifier) << "Soft verification failures in " @@ -5221,12 +5222,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, result.kind = FailureKind::kSoftFailure; if (method != nullptr && !CanCompilerHandleVerificationFailure(verifier.encountered_failure_types_)) { - method->SetDontCompile(); + set_dont_compile = true; } } if (method != nullptr) { if (verifier.HasInstructionThatWillThrow()) { - method->SetDontCompile(); + set_dont_compile = true; if (aot_mode && (callbacks != nullptr) && !callbacks->IsBootImage()) { // When compiling apps, make HasInstructionThatWillThrow a soft error to trigger // re-verification at runtime. @@ -5240,9 +5241,12 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, result.kind = FailureKind::kSoftFailure; } } + bool must_count_locks = false; if ((verifier.encountered_failure_types_ & VerifyError::VERIFY_ERROR_LOCKING) != 0) { - method->SetMustCountLocks(); + must_count_locks = true; } + verifier_callback->SetDontCompile(method, set_dont_compile); + verifier_callback->SetMustCountLocks(method, must_count_locks); } } else { // Bad method data. diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index 09d384a069..83dafd3975 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -72,6 +72,16 @@ enum RegisterTrackingMode { kTrackRegsAll, }; +// A class used by the verifier to tell users about what options need to be set for given methods. +class VerifierCallback { + public: + virtual ~VerifierCallback() {} + virtual void SetDontCompile(ArtMethod* method, bool value) + REQUIRES_SHARED(Locks::mutator_lock_) = 0; + virtual void SetMustCountLocks(ArtMethod* method, bool value) + REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + // A mapping from a dex pc to the register line statuses as they are immediately prior to the // execution of that instruction. class PcToRegisterLineTable { @@ -248,12 +258,12 @@ class MethodVerifier { ArtMethod* method, uint32_t method_access_flags, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, uint32_t api_level, bool aot_mode, - bool allow_suspension, std::string* hard_failure_msg) REQUIRES_SHARED(Locks::mutator_lock_); @@ -270,12 +280,12 @@ class MethodVerifier { ArtMethod* method, uint32_t method_access_flags, CompilerCallbacks* callbacks, + VerifierCallback* verifier_callback, bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, uint32_t api_level, bool aot_mode, - bool allow_suspension, std::string* hard_failure_msg) REQUIRES_SHARED(Locks::mutator_lock_); diff --git a/test/1948-obsolete-const-method-handle/util-src/build-classes b/test/1948-obsolete-const-method-handle/util-src/build-classes index 1b2d79a060..af22dd4df3 100755 --- a/test/1948-obsolete-const-method-handle/util-src/build-classes +++ b/test/1948-obsolete-const-method-handle/util-src/build-classes @@ -38,7 +38,7 @@ if [[ ! -d "${BUILD_PATH}" ]]; then fi # Build the initial class files. -(cd "${SRC_PATH}" && javac -cp "${ASM_CLASSPATH}" -d "${BUILD_PATH}" Main.java art/*.java art/constmethodhandle/*.java) || fail "javac error" +(cd "${SRC_PATH}" && javac -source 8 -target 8 -cp "${ASM_CLASSPATH}" -d "${BUILD_PATH}" Main.java art/*.java art/constmethodhandle/*.java) || fail "javac error" # Modify the class files using ASM (cd "${SCRIPT_PATH}" && java -cp "${ASM_CLASSPATH}:${BUILD_PATH}" art.constmethodhandle.TestGenerator "${BUILD_PATH}" "$D8") || fail "generator failure" # Remove the modification classes. We don't need nor want them for the actual test. diff --git a/test/1988-multi-structural-redefine/expected.txt b/test/1988-multi-structural-redefine/expected.txt new file mode 100644 index 0000000000..00aea886c4 --- /dev/null +++ b/test/1988-multi-structural-redefine/expected.txt @@ -0,0 +1,5 @@ +hello - Transform 1 +hello - Transform 2 +Redefining both class art.Test1988$Transform1 and class art.Test1988$Transform2 to use each other. +Transform1 says hi and Transform2 says bye! +Transform2 says hi and Transform1 says bye! diff --git a/test/1988-multi-structural-redefine/info.txt b/test/1988-multi-structural-redefine/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1988-multi-structural-redefine/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1988-multi-structural-redefine/run b/test/1988-multi-structural-redefine/run new file mode 100755 index 0000000000..a36de16ea6 --- /dev/null +++ b/test/1988-multi-structural-redefine/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true diff --git a/test/1988-multi-structural-redefine/src/Main.java b/test/1988-multi-structural-redefine/src/Main.java new file mode 100644 index 0000000000..7e95671cab --- /dev/null +++ b/test/1988-multi-structural-redefine/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1988.run(); + } +} diff --git a/test/1988-multi-structural-redefine/src/art/Redefinition.java b/test/1988-multi-structural-redefine/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1988-multi-structural-redefine/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1988-multi-structural-redefine/src/art/Test1988.java b/test/1988-multi-structural-redefine/src/art/Test1988.java new file mode 100644 index 0000000000..6dab4daa20 --- /dev/null +++ b/test/1988-multi-structural-redefine/src/art/Test1988.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; + +public class Test1988 { + static class Transform1 { + public static void sayHi() { + System.out.println("hello - Transform 1"); + } + } + static class Transform2 { + public static void sayHi() { + System.out.println("hello - Transform 2"); + } + } + + /** Base64 encoded dex file for + * + * static class Trasnform1 { + * public static void sayHi() { + * System.out.println("Transform1 says hi and " + Transform2.getBye()); + * } + * public static String getBye() { + * return "Transform1 says bye!"; + * } + * } + */ + public static final byte[] T1_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAU4pPI4BKgrMtz7s1Ogc8in1PQhazaRWBcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" + + "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" + + "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" + + "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" + + "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" + + "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAgAAAAAAAAAAABUA" + + "AAAAAAIAGQAAAAEAAAAVAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" + + "AAAAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" + + "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAMAAAAMASICCABw" + + "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAAYADgALAA4ACAAOARoPAAAAAAEA" + + "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" + + "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" + + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" + + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" + + "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" + + "YW5zZm9ybTEAFFRyYW5zZm9ybTEgc2F5cyBieWUhABdUcmFuc2Zvcm0xIHNheXMgaGkgYW5kIAAB" + + "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" + + "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" + + "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" + + "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAACAgATIAwEJsAMB" + + "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" + + "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" + + "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" + + "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" + + "mAQAAA=="); + + + /** Base64 encoded dex file for + * + * static class Trasnform2 { + * public static void sayHi() { + * System.out.println("Transform2 says hi and " + Transform1.getBye()); + * } + * public static String getBye() { + * return "Transform2 says bye!"; + * } + * } + */ + public static final byte[] T2_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQD94cwR+R7Yw7VMom5CwuQd5mZlsV2xrVFcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" + + "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" + + "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" + + "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" + + "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" + + "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAAAVAAAAAQACAAAA" + + "AAABAAAAFQAAAAEAAgAZAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" + + "AAEAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" + + "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAAAAAAMASICCABw" + + "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAA4ADgATAA4AEAAOARoPAAAAAAEA" + + "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" + + "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" + + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" + + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" + + "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" + + "YW5zZm9ybTIAFFRyYW5zZm9ybTIgc2F5cyBieWUhABdUcmFuc2Zvcm0yIHNheXMgaGkgYW5kIAAB" + + "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" + + "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" + + "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" + + "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAAGAgATIAwEJsAMB" + + "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" + + "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" + + "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" + + "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" + + "mAQAAA=="); + + + public static void run() { + doTest(); + } + + public static void doTest() { + Transform1.sayHi(); + Transform2.sayHi(); + System.out.println( + "Redefining both " + Transform1.class + " and " + Transform2.class + " to use each other."); + Redefinition.doMultiStructuralClassRedefinition( + new Redefinition.DexOnlyClassDefinition(Transform1.class, T1_BYTES), + new Redefinition.DexOnlyClassDefinition(Transform2.class, T2_BYTES)); + Transform1.sayHi(); + Transform2.sayHi(); + } +} diff --git a/test/1990-structural-bad-verify/expected.txt b/test/1990-structural-bad-verify/expected.txt new file mode 100644 index 0000000000..7478dda33a --- /dev/null +++ b/test/1990-structural-bad-verify/expected.txt @@ -0,0 +1,2 @@ +hello +I say hello and you say goodbye! diff --git a/test/1990-structural-bad-verify/info.txt b/test/1990-structural-bad-verify/info.txt new file mode 100644 index 0000000000..f2ecd68701 --- /dev/null +++ b/test/1990-structural-bad-verify/info.txt @@ -0,0 +1,6 @@ +Tests basic functions in the jvmti plugin. + +b/142876078 + +This tests a crash that could occur which was caused by the dex-cache being in an unexpected +state. diff --git a/test/1990-structural-bad-verify/run b/test/1990-structural-bad-verify/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1990-structural-bad-verify/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1990-structural-bad-verify/src/Main.java b/test/1990-structural-bad-verify/src/Main.java new file mode 100644 index 0000000000..5622925df1 --- /dev/null +++ b/test/1990-structural-bad-verify/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1990.run(); + } +} diff --git a/test/1990-structural-bad-verify/src/art/Redefinition.java b/test/1990-structural-bad-verify/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1990-structural-bad-verify/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1990-structural-bad-verify/src/art/Test1990.java b/test/1990-structural-bad-verify/src/art/Test1990.java new file mode 100644 index 0000000000..90034fca55 --- /dev/null +++ b/test/1990-structural-bad-verify/src/art/Test1990.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; +public class Test1990 { + + static class Transform { + public static void saySomething() { + System.out.println("hello"); + } + } + + /** + * base64 encoded class/dex file for + * static class Transform { + * public static void saySomething() { + * System.out.println("I say hello and " + sayGoodbye()); + * } + * public static String sayGoodbye() { + * return "you say goodbye!"; + * } + * } + */ + // NB The actual dex codes are as follows. This is an explanation of the error this test checks. + // + // The exact order of instructions is important. Notice the 'invoke-static sayGoodbye' + // (instruction 0002) dominates the rest of the block. During the first (runnable) verification + // step the verifier will first check and verify there are no hard-failures in this class. Next it + // will realize it cannot find the sayGoodbye method on the loaded & resolved Transform class. + // This is (correctly) recognized as a soft-verification failure but then the verifier decides the + // rest of the method is dead-code. This means the verifier will not perform any of the + // soft-failure checks on the rest of the method (since control would never reach there). + // + // Later after performing the redefinition we do a reverify. At this time we held an exclusive + // mutator-lock though so it cannot resolve classes and will not add anything to the dex-cache. + // Here we can get past instruction 0002 and successfully determine the rest of the function is + // fine. In the process we filled in the methods into the dex-cache but not the classes. This + // caused this test to crash when run through the interpreter. + // + // #2 : (in Lart/Test1990$Transform;) + // name : 'saySomething' + // type : '()V' + // access : 0x0009 (PUBLIC STATIC) + // code - + // registers : 4 + // ins : 0 + // outs : 2 + // insns size : 27 16-bit code units + // 0001d0: |[0001d0] art.Test1990$Transform.saySomething:()V + // 0001e0: 6200 0000 |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000 + // 0001e4: 7100 0100 0000 |0002: invoke-static {}, Lart/Test1990$Transform;.sayGoodbye:()Ljava/lang/String; // method@0001 + // 0001ea: 0c01 |0005: move-result-object v1 + // 0001ec: 2202 0700 |0006: new-instance v2, Ljava/lang/StringBuilder; // type@0007 + // 0001f0: 7010 0500 0200 |0008: invoke-direct {v2}, Ljava/lang/StringBuilder;.<init>:()V // method@0005 + // 0001f6: 1a03 0100 |000b: const-string v3, "I say hello and " // string@0001 + // 0001fa: 6e20 0600 3200 |000d: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0006 + // 000200: 6e20 0600 1200 |0010: invoke-virtual {v2, v1}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0006 + // 000206: 6e10 0700 0200 |0013: invoke-virtual {v2}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 + // 00020c: 0c01 |0016: move-result-object v1 + // 00020e: 6e20 0300 1000 |0017: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0003 + // 000214: 0e00 |001a: return-void + // catches : (none) + // positions : + // 0x0000 line=5 + // 0x001a line=6 + // locals : + + // Virtual methods - + // source_file_idx : 13 (Test1990.java) + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( +"ZGV4CjAzNQCV0LekDslEGFglxYgCw7HSyxVegIDjERswBQAAcAAAAHhWNBIAAAAAAAAAAGwEAAAc" + +"AAAAcAAAAAoAAADgAAAABAAAAAgBAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAACQAwAAoAEAAC4C" + +"AAA2AgAASAIAAEsCAABPAgAAaQIAAHkCAACdAgAAvQIAANQCAADoAgAA/AIAABcDAAArAwAAOgMA" + +"AEUDAABIAwAATAMAAFkDAABhAwAAZwMAAGwDAAB1AwAAgQMAAI8DAACZAwAAoAMAALIDAAAEAAAA" + +"BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAPAAAAAgAAAAYAAAAAAAAAAwAAAAcAAAAo" + +"AgAADwAAAAkAAAAAAAAAEAAAAAkAAAAoAgAACAAEABQAAAAAAAIAAAAAAAAAAAAWAAAAAAACABcA" + +"AAAEAAMAFQAAAAUAAgAAAAAABwACAAAAAAAHAAEAEgAAAAcAAAAYAAAAAAAAAAAAAAAFAAAAAAAA" + +"AA0AAABcBAAAOQQAAAAAAAABAAAAAAAAABoCAAADAAAAGgAaABEAAAABAAEAAQAAABYCAAAEAAAA" + +"cBAEAAAADgAEAAAAAgAAAB4CAAAbAAAAYgAAAHEAAQAAAAwBIgIHAHAQBQACABoDAQBuIAYAMgBu" + +"IAYAEgBuEAcAAgAMAW4gAwAQAA4AAwAOAAgADgAFAA4BGg8AAAAAAQAAAAYABjxpbml0PgAQSSBz" + +"YXkgaGVsbG8gYW5kIAABTAACTEwAGExhcnQvVGVzdDE5OTAkVHJhbnNmb3JtOwAOTGFydC9UZXN0" + +"MTk5MDsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" + +"dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" + +"dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwASTGphdmEv" + +"bGFuZy9TeXN0ZW07AA1UZXN0MTk5MC5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFn" + +"cwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4ACnNheUdvb2RieWUADHNheVNvbWV0aGluZwAI" + +"dG9TdHJpbmcABXZhbHVlABB5b3Ugc2F5IGdvb2RieWUhAHZ+fkQ4eyJjb21waWxhdGlvbi1tb2Rl" + +"IjoiZGVidWciLCJtaW4tYXBpIjoxLCJzaGEtMSI6IjYwZGE0ZDY3YjM4MWM0MjQ2Nzc1N2M0OWZi" + +"NmU1NTc1NmQ4OGEyZjMiLCJ2ZXJzaW9uIjoiMS43LjEyLWRldiJ9AAICARkYAQIDAhEECBMXDgAA" + +"AwAAgIAEuAMBCaADAQnQAwAAAAAAAgAAACoEAAAwBAAAUAQAAAAAAAAAAAAAAAAAABAAAAAAAAAA" + +"AQAAAAAAAAABAAAAHAAAAHAAAAACAAAACgAAAOAAAAADAAAABAAAAAgBAAAEAAAAAQAAADgBAAAF" + +"AAAACAAAAEABAAAGAAAAAQAAAIABAAABIAAAAwAAAKABAAADIAAAAwAAABYCAAABEAAAAQAAACgC" + +"AAACIAAAHAAAAC4CAAAEIAAAAgAAACoEAAAAIAAAAQAAADkEAAADEAAAAgAAAEwEAAAGIAAAAQAA" + +"AFwEAAAAEAAAAQAAAGwEAAA="); + + + + public static void run() throws Exception { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(new Transform()); + } + + public static void doTest(Transform t) throws Exception { + Transform.saySomething(); + Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); + Transform.saySomething(); + } +} diff --git a/test/1991-hello-structural-retransform/expected.txt b/test/1991-hello-structural-retransform/expected.txt new file mode 100644 index 0000000000..7478dda33a --- /dev/null +++ b/test/1991-hello-structural-retransform/expected.txt @@ -0,0 +1,2 @@ +hello +I say hello and you say goodbye! diff --git a/test/1991-hello-structural-retransform/info.txt b/test/1991-hello-structural-retransform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1991-hello-structural-retransform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1991-hello-structural-retransform/run b/test/1991-hello-structural-retransform/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1991-hello-structural-retransform/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1991-hello-structural-retransform/src/Main.java b/test/1991-hello-structural-retransform/src/Main.java new file mode 100644 index 0000000000..531ca4afaf --- /dev/null +++ b/test/1991-hello-structural-retransform/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1991.run(); + } +} diff --git a/test/1991-hello-structural-retransform/src/art/Redefinition.java b/test/1991-hello-structural-retransform/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1991-hello-structural-retransform/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1991-hello-structural-retransform/src/art/Test1991.java b/test/1991-hello-structural-retransform/src/art/Test1991.java new file mode 100644 index 0000000000..6060c202cd --- /dev/null +++ b/test/1991-hello-structural-retransform/src/art/Test1991.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; +public class Test1991 { + + static class Transform { + public static void sayHi() { + System.out.println("hello"); + } + } + + + /** + * base64 encoded class/dex file for + * static class Transform { + * public static void sayHi() { + * System.out.println("I say hello and " + sayGoodbye()); + * } + * public static String sayGoodbye() { + * return "you say goodbye!"; + * } + * } + */ + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCi0OGZvVpTRbHGfNbo3bfcu60kPpJayMgoBQAAcAAAAHhWNBIAAAAAAAAAAGQEAAAc" + + "AAAAcAAAAAoAAADgAAAABAAAAAgBAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAACIAwAAoAEAAC4C" + + "AAA2AgAASAIAAEsCAABPAgAAaQIAAHkCAACdAgAAvQIAANQCAADoAgAA/AIAABcDAAArAwAAOgMA" + + "AEUDAABIAwAATAMAAFkDAABhAwAAZwMAAGwDAAB1AwAAgQMAAIgDAACSAwAAmQMAAKsDAAAEAAAA" + + "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAPAAAAAgAAAAYAAAAAAAAAAwAAAAcAAAAo" + + "AgAADwAAAAkAAAAAAAAAEAAAAAkAAAAoAgAACAAEABQAAAAAAAIAAAAAAAAAAAAWAAAAAAACABcA" + + "AAAEAAMAFQAAAAUAAgAAAAAABwACAAAAAAAHAAEAEgAAAAcAAAAYAAAAAAAAAAAAAAAFAAAAAAAA" + + "AA0AAABUBAAAMgQAAAAAAAABAAAAAAAAABoCAAADAAAAGgAaABEAAAABAAEAAQAAABYCAAAEAAAA" + + "cBAEAAAADgAEAAAAAgAAAB4CAAAbAAAAYgAAAHEAAQAAAAwBIgIHAHAQBQACABoDAQBuIAYAMgBu" + + "IAYAEgBuEAcAAgAMAW4gAwAQAA4ABgAOAAsADgAIAA4BGg8AAAAAAQAAAAYABjxpbml0PgAQSSBz" + + "YXkgaGVsbG8gYW5kIAABTAACTEwAGExhcnQvVGVzdDE5OTEkVHJhbnNmb3JtOwAOTGFydC9UZXN0" + + "MTk5MTsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" + + "dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" + + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA1UZXN0MTk5MS5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFn" + + "cwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4ACnNheUdvb2RieWUABXNheUhpAAh0b1N0cmlu" + + "ZwAFdmFsdWUAEHlvdSBzYXkgZ29vZGJ5ZSEAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1" + + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2" + + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgIBGRgBAgMCEQQIExcOAAADAACAgAS4" + + "AwEJoAMBCdADAAAAAAIAAAAjBAAAKQQAAEgEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAA" + + "AQAAABwAAABwAAAAAgAAAAoAAADgAAAAAwAAAAQAAAAIAQAABAAAAAEAAAA4AQAABQAAAAgAAABA" + + "AQAABgAAAAEAAACAAQAAASAAAAMAAACgAQAAAyAAAAMAAAAWAgAAARAAAAEAAAAoAgAAAiAAABwA" + + "AAAuAgAABCAAAAIAAAAjBAAAACAAAAEAAAAyBAAAAxAAAAIAAABEBAAABiAAAAEAAABUBAAAABAA" + + "AAEAAABkBAAA"); + + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.STRUCTURAL_TRANSFORM); + doTest(); + } + + public static void doTest() { + Transform.sayHi(); + Redefinition.addCommonTransformationResult("art/Test1991$Transform", new byte[0], DEX_BYTES); + Redefinition.enableCommonRetransformation(true); + Redefinition.doCommonClassRetransformation(Transform.class); + Transform.sayHi(); + } +} diff --git a/test/1992-retransform-no-such-field/expected.txt b/test/1992-retransform-no-such-field/expected.txt new file mode 100644 index 0000000000..53de32a31a --- /dev/null +++ b/test/1992-retransform-no-such-field/expected.txt @@ -0,0 +1,2 @@ +This file was written in the year 2019! +This new class was written in 2019 diff --git a/test/1992-retransform-no-such-field/info.txt b/test/1992-retransform-no-such-field/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1992-retransform-no-such-field/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1992-retransform-no-such-field/run b/test/1992-retransform-no-such-field/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1992-retransform-no-such-field/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti diff --git a/test/1992-retransform-no-such-field/src/Main.java b/test/1992-retransform-no-such-field/src/Main.java new file mode 100644 index 0000000000..ccffb88e7a --- /dev/null +++ b/test/1992-retransform-no-such-field/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1992.run(); + } +} diff --git a/test/1992-retransform-no-such-field/src/art/Redefinition.java b/test/1992-retransform-no-such-field/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1992-retransform-no-such-field/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1992-retransform-no-such-field/src/art/Test1992.java b/test/1992-retransform-no-such-field/src/art/Test1992.java new file mode 100644 index 0000000000..5e1d5bb349 --- /dev/null +++ b/test/1992-retransform-no-such-field/src/art/Test1992.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package art; + +import java.util.Base64; +public class Test1992 { + public static boolean FAIL_IT = false; + + static class Transform { + public void saySomething() { + System.out.println("This file was written in the year 2019!"); + } + } + + /** + * base64 encoded class/dex file for + * class Transform { + * public void saySomething() { + * if (Test1992.FAIL_IT) { + * // Force verification soft-fail. + * Test1992.NOT_THERE = 1; + * } + * System.out.println("This new class was written in " + year); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( +"yv66vgAAADUAKQoACAARCQASABMJABIAFAkAFQAWCAAXCgAYABkHABoHAB0BAAY8aW5pdD4BAAMo" + +"KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAMc2F5U29tZXRoaW5nAQANU3RhY2tNYXBUYWJs" + +"ZQEAClNvdXJjZUZpbGUBAA1UZXN0MTk5Mi5qYXZhDAAJAAoHAB4MAB8AIAwAIQAiBwAjDAAkACUB" + +"ACJUaGlzIG5ldyBjbGFzcyB3YXMgd3JpdHRlbiBpbiAyMDE5BwAmDAAnACgBABZhcnQvVGVzdDE5" + +"OTIkVHJhbnNmb3JtAQAJVHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVj" + +"dAEADGFydC9UZXN0MTk5MgEAB0ZBSUxfSVQBAAFaAQAJTk9UX1RIRVJFAQABSQEAEGphdmEvbGFu" + +"Zy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3Ry" + +"ZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAgAAcACAAAAAAAAgAAAAkACgAB" + +"AAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAUAAQANAAoAAQALAAAAQAACAAEAAAAT" + +"sgACmQAHBLMAA7IABBIFtgAGsQAAAAIADAAAABIABAAAAAkABgAKAAoADAASAA0ADgAAAAMAAQoA" + +"AgAPAAAAAgAQABwAAAAKAAEABwASABsACA=="); + + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( +"ZGV4CjAzNQAWyivK2j0yR4u2YH/R8Bs3KIFv7O/Hs0WkBAAAcAAAAHhWNBIAAAAAAAAAAOADAAAZ" + +"AAAAcAAAAAsAAADUAAAAAgAAAAABAAADAAAAGAEAAAQAAAAwAQAAAQAAAFABAAA0AwAAcAEAAMoB" + +"AADSAQAA2wEAAN4BAAD4AQAACAIAACwCAABMAgAAYwIAAHcCAACLAgAAnwIAAKoCAAC5AgAA3QIA" + +"AOgCAADrAgAA7wIAAPICAAD/AgAABQMAAAoDAAATAwAAIQMAACgDAAACAAAAAwAAAAQAAAAFAAAA" + +"BgAAAAcAAAAIAAAACQAAAAoAAAAPAAAAEQAAAA8AAAAJAAAAAAAAABAAAAAJAAAAxAEAAAIACgAB" + +"AAAAAgAAAAsAAAAIAAUAFAAAAAEAAAAAAAAAAQAAABYAAAAFAAEAFQAAAAYAAAAAAAAAAQAAAAAA" + +"AAAGAAAAAAAAAAwAAADQAwAArwMAAAAAAAABAAEAAQAAALYBAAAEAAAAcBADAAAADgADAAEAAgAA" + +"ALoBAAAPAAAAYwAAADgABQASEGcAAQBiAAIAGgENAG4gAgAQAA4ABQAOAAkADks9eAAAAAABAAAA" + +"BwAGPGluaXQ+AAdGQUlMX0lUAAFJABhMYXJ0L1Rlc3QxOTkyJFRyYW5zZm9ybTsADkxhcnQvVGVz" + +"dDE5OTI7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90" + +"YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl" + +"Y3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAJTk9UX1RIRVJFAA1U" + +"ZXN0MTk5Mi5qYXZhACJUaGlzIG5ldyBjbGFzcyB3YXMgd3JpdHRlbiBpbiAyMDE5AAlUcmFuc2Zv" + +"cm0AAVYAAlZMAAFaAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxuAAxzYXlTb21ldGhp" + +"bmcABXZhbHVlAHZ+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4tYXBpIjoxLCJz" + +"aGEtMSI6IjYwZGE0ZDY3YjM4MWM0MjQ2Nzc1N2M0OWZiNmU1NTc1NmQ4OGEyZjMiLCJ2ZXJzaW9u" + +"IjoiMS43LjEyLWRldiJ9AAIDARcYAgIEAhIECBMXDgAAAQEAgIAE8AIBAYgDAAAAAAAAAAIAAACg" + +"AwAApgMAAMQDAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAAAQAAABkAAABwAAAAAgAAAAsA" + +"AADUAAAAAwAAAAIAAAAAAQAABAAAAAMAAAAYAQAABQAAAAQAAAAwAQAABgAAAAEAAABQAQAAASAA" + +"AAIAAABwAQAAAyAAAAIAAAC2AQAAARAAAAEAAADEAQAAAiAAABkAAADKAQAABCAAAAIAAACgAwAA" + +"ACAAAAEAAACvAwAAAxAAAAIAAADAAwAABiAAAAEAAADQAwAAABAAAAEAAADgAwAA"); + + + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.saySomething(); + Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + t.saySomething(); + } +} diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt new file mode 100644 index 0000000000..7e2cdf4ee5 --- /dev/null +++ b/test/1993-fallback-non-structural/expected.txt @@ -0,0 +1,3 @@ +Can structurally Redefine: false +hello +Goodbye diff --git a/test/1993-fallback-non-structural/info.txt b/test/1993-fallback-non-structural/info.txt new file mode 100644 index 0000000000..3b558e1cd1 --- /dev/null +++ b/test/1993-fallback-non-structural/info.txt @@ -0,0 +1,4 @@ +Tests basic functions in the jvmti plugin. + +Tests that using the structural redefinition functions will fall back to non-structural +redefinition when possible. diff --git a/test/1993-fallback-non-structural/run b/test/1993-fallback-non-structural/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1993-fallback-non-structural/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1993-fallback-non-structural/src/Main.java b/test/1993-fallback-non-structural/src/Main.java new file mode 100644 index 0000000000..61e060c773 --- /dev/null +++ b/test/1993-fallback-non-structural/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1993.run(); + } +} diff --git a/test/1993-fallback-non-structural/src/art/Redefinition.java b/test/1993-fallback-non-structural/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1993-fallback-non-structural/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java new file mode 100644 index 0000000000..f4420993f7 --- /dev/null +++ b/test/1993-fallback-non-structural/src/art/Test1993.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; +public class Test1993 { + + static class Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } + } + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDxrdbiBcsn0r58mdtcdyDxVUxwbWfShNQwBAAAcAAAAHhWNBIAAAAAAAAAAGwDAAAV" + + "AAAAcAAAAAkAAADEAAAAAgAAAOgAAAABAAAAAAEAAAQAAAAIAQAAAQAAACgBAADoAgAASAEAAJIB" + + "AACaAQAAowEAAL0BAADNAQAA8QEAABECAAAoAgAAPAIAAFACAABkAgAAcwIAAH4CAACBAgAAhQIA" + + "AJICAACYAgAAnQIAAKYCAACtAgAAtAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" + + "DAAAAAwAAAAIAAAAAAAAAA0AAAAIAAAAjAEAAAcABAAQAAAAAAAAAAAAAAAAAAAAEgAAAAQAAQAR" + + "AAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAACgAAAFwDAAA7AwAAAAAAAAEAAQABAAAAgAEAAAQA" + + "AABwEAMAAAAOAAMAAQACAAAAhAEAAAgAAABiAAAAGgEBAG4gAgAQAA4AAwAOAAUADngAAAAAAQAA" + + "AAYABjxpbml0PgAHR29vZGJ5ZQAYTGFydC9UZXN0MTk5MyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3Qx" + + "OTkzOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0" + + "aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0" + + "OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTkzLmphdmEA" + + "CVRyYW5zZm9ybQABVgACVkwAC2FjY2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhp" + + "AAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hh" + + "LTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFhZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6" + + "IjEuNy4xNC1kZXYifQACAgETGAECAwIOBAgPFwsAAAEBAICABMgCAQHgAgAAAAAAAAACAAAALAMA" + + "ADIDAABQAwAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAJAAAA" + + "xAAAAAMAAAACAAAA6AAAAAQAAAABAAAAAAEAAAUAAAAEAAAACAEAAAYAAAABAAAAKAEAAAEgAAAC" + + "AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" + + "AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA=="); + + public static void run() { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + // TODO Remove this once the class is structurally modifiable. + System.out.println("Can structurally Redefine: " + + Redefinition.isStructurallyModifiable(Transform.class)); + t.sayHi(); + Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); + t.sayHi(); + } +} diff --git a/test/1997-structural-shadow-method/expected.txt b/test/1997-structural-shadow-method/expected.txt new file mode 100644 index 0000000000..3a8b8de39b --- /dev/null +++ b/test/1997-structural-shadow-method/expected.txt @@ -0,0 +1,6 @@ +Hello! +Hello! +Hello! +Hello World! +Hello World! +Hello World! diff --git a/test/1997-structural-shadow-method/info.txt b/test/1997-structural-shadow-method/info.txt new file mode 100644 index 0000000000..71e3bfcd2e --- /dev/null +++ b/test/1997-structural-shadow-method/info.txt @@ -0,0 +1 @@ +Test structural redefinition when the method being added was resolvable previously. diff --git a/test/1997-structural-shadow-method/run b/test/1997-structural-shadow-method/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1997-structural-shadow-method/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1997-structural-shadow-method/src/Main.java b/test/1997-structural-shadow-method/src/Main.java new file mode 100644 index 0000000000..3c9bc85fb3 --- /dev/null +++ b/test/1997-structural-shadow-method/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1997.run(); + } +} diff --git a/test/1997-structural-shadow-method/src/art/Redefinition.java b/test/1997-structural-shadow-method/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1997-structural-shadow-method/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1997-structural-shadow-method/src/art/Test1997.java b/test/1997-structural-shadow-method/src/art/Test1997.java new file mode 100644 index 0000000000..7309a31bd3 --- /dev/null +++ b/test/1997-structural-shadow-method/src/art/Test1997.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; + +public class Test1997 { + + public static class SuperTransform { + // We will be shadowing this function. + public static void sayHi() { + System.out.println("Hello!"); + } + } + + // The class we will be transforming. + public static class Transform extends SuperTransform { + public static void sayHiTwice() { + Transform.sayHi(); + Transform.sayHi(); + } + } + + // public static class Transform extends SuperTransform { + // public static void sayHiTwice() { + // Transform.sayHi(); + // Transform.sayHi(); + // } + // public static void sayHi() { + // System.out.println("Hello World!"); + // } + // } + private static final byte[] DEX_BYTES = + Base64.getDecoder() + .decode( + "ZGV4CjAzNQA9wdy7Lgbrv+sD+wixborREr0maZCK5yqABAAAcAAAAHhWNBIAAAAAAAAAALwDAAAW" + + "AAAAcAAAAAkAAADIAAAAAgAAAOwAAAABAAAABAEAAAUAAAAMAQAAAQAAADQBAAAsAwAAVAEAAMIB" + + "AADKAQAA2AEAAPcBAAARAgAAIQIAAEUCAABlAgAAfAIAAJACAACkAgAAswIAAL4CAADBAgAAxQIA" + + "ANICAADYAgAA3QIAAOYCAADtAgAA+QIAAAADAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA" + + "CQAAAAwAAAAMAAAACAAAAAAAAAANAAAACAAAALwBAAAHAAUAEAAAAAAAAAAAAAAAAQAAAAAAAAAB" + + "AAAAEgAAAAEAAAATAAAABQABABEAAAABAAAAAQAAAAAAAAAAAAAACgAAAKwDAACHAwAAAAAAAAEA" + + "AQABAAAAqgEAAAQAAABwEAAAAAAOAAIAAAACAAAArgEAAAgAAABiAAAAGgEBAG4gBAAQAA4AAAAA" + + "AAAAAACzAQAABwAAAHEAAgAAAHEAAgAAAA4ADwAOABUADngAEQAOPDwAAAAAAQAAAAYABjxpbml0" + + "PgAMSGVsbG8gV29ybGQhAB1MYXJ0L1Rlc3QxOTk3JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9UZXN0" + + "MTk5NyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" + + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" + + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA1UZXN0MTk5" + + "Ny5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxu" + + "AAVzYXlIaQAKc2F5SGlUd2ljZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1" + + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2" + + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgMBFBgCAgQCDgQJDxcLAAADAAGBgATU" + + "AgEJ7AIBCYwDAAAAAAAAAAIAAAB4AwAAfgMAAKADAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAA" + + "AAAAAQAAABYAAABwAAAAAgAAAAkAAADIAAAAAwAAAAIAAADsAAAABAAAAAEAAAAEAQAABQAAAAUA" + + "AAAMAQAABgAAAAEAAAA0AQAAASAAAAMAAABUAQAAAyAAAAMAAACqAQAAARAAAAEAAAC8AQAAAiAA" + + "ABYAAADCAQAABCAAAAIAAAB4AwAAACAAAAEAAACHAwAAAxAAAAIAAACcAwAABiAAAAEAAACsAwAA" + + "ABAAAAEAAAC8AwAA"); + + public static void run() throws Exception { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(); + } + + public static void doTest() throws Exception { + Transform.sayHiTwice(); + Transform.sayHi(); + Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); + Transform.sayHiTwice(); + Transform.sayHi(); + } +} diff --git a/test/1998-structural-shadow-field/expected.txt b/test/1998-structural-shadow-field/expected.txt new file mode 100644 index 0000000000..9ae530e728 --- /dev/null +++ b/test/1998-structural-shadow-field/expected.txt @@ -0,0 +1,4 @@ +Hello +Hello +null +Hello diff --git a/test/1998-structural-shadow-field/info.txt b/test/1998-structural-shadow-field/info.txt new file mode 100644 index 0000000000..71e3bfcd2e --- /dev/null +++ b/test/1998-structural-shadow-field/info.txt @@ -0,0 +1 @@ +Test structural redefinition when the method being added was resolvable previously. diff --git a/test/1998-structural-shadow-field/run b/test/1998-structural-shadow-field/run new file mode 100755 index 0000000000..03e41a58e7 --- /dev/null +++ b/test/1998-structural-shadow-field/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true diff --git a/test/1998-structural-shadow-field/src/Main.java b/test/1998-structural-shadow-field/src/Main.java new file mode 100644 index 0000000000..f6aeca5b85 --- /dev/null +++ b/test/1998-structural-shadow-field/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1998.run(); + } +} diff --git a/test/1998-structural-shadow-field/src/art/Redefinition.java b/test/1998-structural-shadow-field/src/art/Redefinition.java new file mode 120000 index 0000000000..81eaf31bbb --- /dev/null +++ b/test/1998-structural-shadow-field/src/art/Redefinition.java @@ -0,0 +1 @@ +../../../jvmti-common/Redefinition.java
\ No newline at end of file diff --git a/test/1998-structural-shadow-field/src/art/Test1998.java b/test/1998-structural-shadow-field/src/art/Test1998.java new file mode 100644 index 0000000000..3fda936f84 --- /dev/null +++ b/test/1998-structural-shadow-field/src/art/Test1998.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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. + */ + +package art; + +import java.util.Base64; + +public class Test1998 { + + public static class SuperTransform { + public static String greeting = "Hello"; + } + + // The class we will be transforming. + public static class Transform extends SuperTransform { } + + // public static class Transform extends SuperTransform { + // public static String greeting; + // } + private static final byte[] DEX_BYTES = + Base64.getDecoder() + .decode( +"ZGV4CjAzNQCYmnoWz4BqygrZQM4zf/mJ/25+dM86MHKAAwAAcAAAAHhWNBIAAAAAAAAAAMgCAAAP" + +"AAAAcAAAAAcAAACsAAAAAQAAAMgAAAABAAAA1AAAAAIAAADcAAAAAQAAAOwAAAB0AgAADAEAACgB" + +"AAAwAQAATwEAAGkBAAB5AQAAnQEAAL0BAADRAQAA4AEAAOsBAADuAQAA+wEAAAUCAAALAgAAEgIA" + +"AAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAkAAAAJAAAABgAAAAAAAAABAAUACwAAAAAAAAAAAAAA" + +"AQAAAAAAAAABAAAAAQAAAAAAAAAAAAAABwAAALgCAACZAgAAAAAAAAEAAQABAAAAJAEAAAQAAABw" + +"EAAAAAAOAAUADgAGPGluaXQ+AB1MYXJ0L1Rlc3QxOTk4JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9U" + +"ZXN0MTk5OCRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0Vu" + +"Y2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5n" + +"L1N0cmluZzsADVRlc3QxOTk4LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MACGdyZWV0" + +"aW5nAARuYW1lAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFw" + +"aSI6MSwic2hhLTEiOiI2MGRhNGQ2N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwi" + +"dmVyc2lvbiI6IjEuNy4xMi1kZXYifQACAwENGAICBAIKBAkMFwgBAAEAAAkBgYAEjAIAAAAAAAAA" + +"AgAAAIoCAACQAgAArAIAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAAC" + +"AAAABwAAAKwAAAADAAAAAQAAAMgAAAAEAAAAAQAAANQAAAAFAAAAAgAAANwAAAAGAAAAAQAAAOwA" + +"AAABIAAAAQAAAAwBAAADIAAAAQAAACQBAAACIAAADwAAACgBAAAEIAAAAgAAAIoCAAAAIAAAAQAA" + +"AJkCAAADEAAAAgAAAKgCAAAGIAAAAQAAALgCAAAAEAAAAQAAAMgCAAA="); + + public static void run() throws Exception { + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + doTest(); + } + + public static void doTest() throws Exception { + System.out.println(Transform.greeting); + System.out.println(SuperTransform.greeting); + Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); + System.out.println(Transform.greeting); + System.out.println(SuperTransform.greeting); + } +} diff --git a/test/580-fp16/src-art/Main.java b/test/580-fp16/src-art/Main.java index 798b52dd34..4aa8d55faa 100644 --- a/test/580-fp16/src-art/Main.java +++ b/test/580-fp16/src-art/Main.java @@ -28,27 +28,71 @@ public class Main { return Float.floatToRawIntBits(f); } - public static void assertEquals(int expected, int actual) { - if (expected != actual) { - throw new Error("Expected: " + expected + ", found: " + actual); + public static void assertEquals(short expected, short calculated) { + if (expected != calculated) { + throw new Error("Expected: " + expected + ", Calculated: " + calculated); } } - - public static void assertEquals(float expected, float actual) { - if (expected != actual) { - throw new Error("Expected: " + expected + ", found: " + actual); + public static void assertEquals(float expected, float calculated) { + if (expected != calculated) { + throw new Error("Expected: " + expected + ", Calculated: " + calculated); } } - public static void main(String args[]) { - // Test FP16 to float + public static void testHalfToFloatToHalfConversions(){ + // Test FP16 to float and back to Half for all possible Short values for (short h = Short.MIN_VALUE; h < Short.MAX_VALUE; h++) { if (FP16.isNaN(h)) { // NaN inputs are tested below. continue; } - assertEquals(FP16.toHalf(FP16.toFloat(h)), h); + assertEquals(h, FP16.toHalf(FP16.toFloat(h))); } + } + + public static void testToHalf(){ + // These asserts check some known values and edge cases for FP16.toHalf + // and have been inspired by the cts HalfTest. + // Zeroes, NaN and infinities + assertEquals(FP16.POSITIVE_ZERO, FP16.toHalf(0.0f)); + assertEquals(FP16.NEGATIVE_ZERO, FP16.toHalf(-0.0f)); + assertEquals(FP16.NaN, FP16.toHalf(Float.NaN)); + assertEquals(FP16.POSITIVE_INFINITY, FP16.toHalf(Float.POSITIVE_INFINITY)); + assertEquals(FP16.NEGATIVE_INFINITY, FP16.toHalf(Float.NEGATIVE_INFINITY)); + // Known values + assertEquals((short) 0x3c01, FP16.toHalf(1.0009765625f)); + assertEquals((short) 0xc000, FP16.toHalf(-2.0f)); + assertEquals((short) 0x0400, FP16.toHalf(6.10352e-5f)); + assertEquals((short) 0x7bff, FP16.toHalf(65504.0f)); + assertEquals((short) 0x3555, FP16.toHalf(1.0f / 3.0f)); + // Subnormals + assertEquals((short) 0x03ff, FP16.toHalf(6.09756e-5f)); + assertEquals(FP16.MIN_VALUE, FP16.toHalf(5.96046e-8f)); + assertEquals((short) 0x83ff, FP16.toHalf(-6.09756e-5f)); + assertEquals((short) 0x8001, FP16.toHalf(-5.96046e-8f)); + // Subnormals (flushed to +/-0) + assertEquals(FP16.POSITIVE_ZERO, FP16.toHalf(5.96046e-9f)); + assertEquals(FP16.NEGATIVE_ZERO, FP16.toHalf(-5.96046e-9f)); + // Test for values that overflow the mantissa bits into exp bits + assertEquals(0x1000, FP16.toHalf(Float.intBitsToFloat(0x39fff000))); + assertEquals(0x0400, FP16.toHalf(Float.intBitsToFloat(0x387fe000))); + // Floats with absolute value above +/-65519 are rounded to +/-inf + // when using round-to-even + assertEquals(0x7bff, FP16.toHalf(65519.0f)); + assertEquals(0x7bff, FP16.toHalf(65519.9f)); + assertEquals(FP16.POSITIVE_INFINITY, FP16.toHalf(65520.0f)); + assertEquals(FP16.NEGATIVE_INFINITY, FP16.toHalf(-65520.0f)); + // Check if numbers are rounded to nearest even when they + // cannot be accurately represented by Half + assertEquals(0x6800, FP16.toHalf(2049.0f)); + assertEquals(0x6c00, FP16.toHalf(4098.0f)); + assertEquals(0x7000, FP16.toHalf(8196.0f)); + assertEquals(0x7400, FP16.toHalf(16392.0f)); + assertEquals(0x7800, FP16.toHalf(32784.0f)); + + } + + public static void testToFloat(){ // FP16 SNaN/QNaN inputs to float // The most significant bit of mantissa: // V @@ -67,4 +111,10 @@ public class Main { assertEquals(0xffc00000, TestFP16ToFloatRawIntBits((short)(0xfe00))); // QNaN->QNaN assertEquals(0xffffe000, TestFP16ToFloatRawIntBits((short)(0xffff))); // QNaN->QNaN } + + public static void main(String args[]) { + testHalfToFloatToHalfConversions(); + testToHalf(); + testToFloat(); + } } diff --git a/test/684-checker-simd-dotprod/src/Main.java b/test/684-checker-simd-dotprod/src/Main.java index e0c87161dd..aa03d1e4a5 100644 --- a/test/684-checker-simd-dotprod/src/Main.java +++ b/test/684-checker-simd-dotprod/src/Main.java @@ -17,6 +17,7 @@ import other.TestByte; import other.TestCharShort; import other.TestVarious; +import other.TestFloatDouble; /** * Tests for dot product idiom vectorization. @@ -26,6 +27,7 @@ public class Main { TestByte.run(); TestCharShort.run(); TestVarious.run(); + TestFloatDouble.run(); System.out.println("passed"); } } diff --git a/test/684-checker-simd-dotprod/src/other/TestFloatDouble.java b/test/684-checker-simd-dotprod/src/other/TestFloatDouble.java new file mode 100644 index 0000000000..b155ae1555 --- /dev/null +++ b/test/684-checker-simd-dotprod/src/other/TestFloatDouble.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +package other; + +/** + * Tests for dot product idiom vectorization: char and short case. + */ +public class TestFloatDouble { + + public static final int ARRAY_SIZE = 1024; + + + /// CHECK-START-{X86_64}: float other.TestFloatDouble.testDotProdSimpleFloat(float[], float[]) loop_optimization (after) + /// CHECK-NOT: VecDotProd + public static final float testDotProdSimpleFloat(float[] a, float[] b) { + float sum = 0; + for (int i = 0; i < b.length; i++) { + sum += a[i] * b[i]; + } + return sum; + } + + + /// CHECK-START-{X86_64}: double other.TestFloatDouble.testDotProdSimpleDouble(double[], double[]) loop_optimization (after) + /// CHECK-NOT: VecDotProd + + public static final double testDotProdSimpleDouble(double[] a, double[] b) { + double sum = 0; + for (int i = 0; i < b.length; i++) { + sum += a[i] * b[i]; + } + return sum; + } + + private static void expectEquals(float expected, float result) { + if (Float.compare(expected, result) != 0) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + private static void expectEquals(double expected, double result) { + if (Double.compare(expected, result) != 0) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public static void run() { + final float MAX_F = Float.MAX_VALUE; + final float MIN_F = Float.MIN_VALUE; + final double MAX_D = Double.MAX_VALUE; + final double MIN_D = Double.MIN_VALUE; + + double[] a = new double[1024]; + for (int i = 0; i != 1024; ++i) a[i] = MAX_D; + double[] b = new double[1024]; + for (int i = 0; i != 1024; ++i) b[i] = ((i & 1) == 0) ? 1.0 : -1.0; + expectEquals(0.0, testDotProdSimpleDouble(a,b)); + + float[] f1_1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.33f, 0.125f, 3.0f, 0.25f}; + float[] f2_1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6.125f, 2.25f, 1.213f, 0.5f}; + expectEquals(24.4415f, testDotProdSimpleFloat(f1_1, f2_1)); + + float [] f1_2 = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0.63671875f, 0.76953125f, 0.22265625f, 1.0f}; + float [] f2_2 = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, MIN_F, MAX_F, MAX_F, MIN_F }; + expectEquals(3.376239E38f, testDotProdSimpleFloat(f1_2, f2_2)); + + float[] f1_3 = { 0xc0000000, 0xc015c28f, 0x411dd42c, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, MIN_F, MIN_F }; + float[] f2_3 = { 0x3f4c779a, 0x408820c5, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0x00000000, 0, MAX_F, MAX_F }; + expectEquals(-2.30124471E18f, testDotProdSimpleFloat(f1_3, f2_3)); + } + + public static void main(String[] args) { + run(); + } +} diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java index af18193388..5a66fbbebc 100644 --- a/test/706-checker-scheduler/src/Main.java +++ b/test/706-checker-scheduler/src/Main.java @@ -322,7 +322,7 @@ public class Main { // but has more complex chains of transforming the original references: // ParameterValue --> BoundType --> NullCheck --> ArrayGet. // ParameterValue --> BoundType --> NullCheck --> IntermediateAddress --> ArraySet. - // After using LSA to analyze the orginal references, the scheduler should be able + // After using LSA to analyze the original references, the scheduler should be able // to find out that 'a' and 'b' may alias, hence unable to schedule these ArraGet/Set. /// CHECK-START-ARM64: void Main.CrossOverLoop2(java.lang.Object, java.lang.Object) scheduler (before) @@ -584,9 +584,126 @@ public class Main { } } + // Check that instructions having cross iteration dependencies are not + // reordered. + // + /// CHECK-START-{ARM,ARM64}: void Main.testCrossItersDependencies() scheduler (before) + /// CHECK: <<ID1:i\d+>> Phi [{{i\d+}},<<ID3:i\d+>>] + /// CHECK: <<ID2:i\d+>> Phi [{{i\d+}},<<ID4:i\d+>>] + // + /// CHECK: <<ID3>> Sub [<<ID1>>,<<ID2>>] + /// CHECK: <<ID4>> Add [<<ID2>>,{{i\d+}}] + + /// CHECK-START-{ARM,ARM64}: void Main.testCrossItersDependencies() scheduler (after) + /// CHECK: <<ID1:i\d+>> Phi [{{i\d+}},<<ID3:i\d+>>] + /// CHECK: <<ID2:i\d+>> Phi [{{i\d+}},<<ID4:i\d+>>] + // + /// CHECK: <<ID3>> Sub [<<ID1>>,<<ID2>>] + /// CHECK: <<ID4>> Add [<<ID2>>,{{i\d+}}] + + /// CHECK-START-ARM: void Main.testCrossItersDependencies() disassembly (after) + /// CHECK: subs + /// CHECK: add + /// CHECK: adds + /// CHECK: ldrh + /// CHECK: cmp + /// CHECK: beq + + /// CHECK-START-ARM64: void Main.testCrossItersDependencies() disassembly (after) + /// CHECK: sub + /// CHECK: add + /// CHECK: add + /// CHECK: ldrh + /// CHECK: cbz + private static void testCrossItersDependencies() { + int[] data = {1, 2, 3, 0}; + int sub = 0; + int sum = data[0]; + for (int i = 1; data[i] != 0; ++i) { + sub -= sum; + sum += data[i]; + } + expectEquals(sub, -4); + expectEquals(sum, 6); + } + + // Check instructions defining values for the next iteration don't become + // self-dependent in a scheduling graph which prevents valid reordering. + // + /// CHECK-START-{ARM,ARM64}: void Main.testNoSelfDependantSchedNode(int) scheduler (before) + /// CHECK: IntermediateAddress + /// CHECK: ArrayGet + /// CHECK: LessThanOrEqual + /// CHECK: Select + /// CHECK: IntermediateAddress + /// CHECK: ArraySet + /// CHECK: Add + + /// CHECK-START-{ARM,ARM64}: void Main.testNoSelfDependantSchedNode(int) scheduler (after) + /// CHECK: IntermediateAddress + /// CHECK: ArrayGet + /// CHECK: IntermediateAddress + /// CHECK: LessThanOrEqual + /// CHECK: Select + /// CHECK: ArraySet + /// CHECK: Add + // + // Parameter n is to prevent unrolling of the main loop. + private static void testNoSelfDependantSchedNode(int n) { + final int MAX = 2; + int[] a = {1, 2, 3}; + int[] b = new int[a.length]; + n = Math.min(n, a.length); + for (int i = 0; i < n; ++i) { + int j = a[i]; + b[i] = (j > MAX ? MAX : 0); + } + expectEquals(b[0], 0); + expectEquals(b[1], 0); + expectEquals(b[2], 2); + } + + // In case of cross iteration dependencies when a value for the next iteration is also used on + // the current iteration a MOV instruction is generated anyway. In such cases setting dependency + // between scheduling nodes will not eliminate MOV. + // In the test 'i+1' is such an example. + // The test checks that a dependency between scheduling nodes (first ArrayGet and Add) is not + // setup and Add is scheduled before ArrayGet. + // + /// CHECK-START-{ARM,ARM64}: void Main.testNonPreventingSchedulingCrossItersDeps(int) scheduler (before) + /// CHECK: IntermediateAddress + /// CHECK-NEXT: ArrayGet + /// CHECK-NEXT: Add + /// CHECK-NEXT: ArrayGet + + /// CHECK-START-{ARM,ARM64}: void Main.testNonPreventingSchedulingCrossItersDeps(int) scheduler (after) + /// CHECK: IntermediateAddress + /// CHECK-NEXT: Add + /// CHECK-NEXT: ArrayGet + /// CHECK-NEXT: ArrayGet + // + // Parameter n is to prevent unrolling of the main loop. + private static void testNonPreventingSchedulingCrossItersDeps(int n) { + int[] a = {1, 2, 3}; + n = Math.min(n, a.length); + for (int i = 0; i < n - 1; ++i) { + if (a[i] < a[i + 1]) { + int tmp = a[i]; + a[i] = a[i + 1]; + a[i + 1] = tmp; + } + } + expectEquals(a[0], 2); + expectEquals(a[1], 3); + expectEquals(a[2], 1); + } + public static void main(String[] args) { testVecSetScalars(); testVecReplicateScalar(); + testCrossItersDependencies(); + testNoSelfDependantSchedNode(3); + testNonPreventingSchedulingCrossItersDeps(3); if ((arrayAccess() + intDiv(10)) != -35) { System.out.println("FAIL"); } diff --git a/test/906-iterate-heap/src/art/Test906.java b/test/906-iterate-heap/src/art/Test906.java index 190f36f5d4..b782c9bd70 100644 --- a/test/906-iterate-heap/src/art/Test906.java +++ b/test/906-iterate-heap/src/art/Test906.java @@ -91,17 +91,26 @@ public class Test906 { public static class Baz extends Bar {} public static class Alpha extends Bar {} public static class MISSING extends Baz {} + public interface Iface {} + public static class Beta implements Iface {} + public static class Gamma implements Iface {} + public static class Delta extends Beta {} private static void testIterateOverInstances() throws Exception { Object[] foos = GenTs(Foo.class); Object[] bars = GenTs(Bar.class); Object[] bazs = GenTs(Baz.class); Object[] alphas = GenTs(Alpha.class); + Object[] betas = GenTs(Beta.class); + Object[] gammas = GenTs(Gamma.class); + Object[] deltas = GenTs(Delta.class); checkEq(0, iterateOverInstancesCount(MISSING.class)); checkEq(alphas.length, iterateOverInstancesCount(Alpha.class)); checkEq(bazs.length, iterateOverInstancesCount(Baz.class)); checkEq(bazs.length + alphas.length + bars.length, iterateOverInstancesCount(Bar.class)); checkEq(bazs.length + alphas.length + bars.length + foos.length, iterateOverInstancesCount(Foo.class)); + checkEq(betas.length + gammas.length + deltas.length, + iterateOverInstancesCount(Iface.class)); } public static void doTest() throws Exception { diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java index 78f6db732f..e70c83b060 100644 --- a/test/956-methodhandles/src/Main.java +++ b/test/956-methodhandles/src/Main.java @@ -27,6 +27,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -94,6 +95,11 @@ public class Main { } } + public static class I { + public static void someVoidMethod() { + } + } + public static void main(String[] args) throws Throwable { testfindSpecial_invokeSuperBehaviour(); testfindSpecial_invokeDirectBehaviour(); @@ -634,6 +640,30 @@ public class Main { fail(); } catch (WrongMethodTypeException expected) { } + + // Zero / null introduction + MethodHandle voidMH = MethodHandles.lookup().findStatic(I.class, "someVoidMethod", + MethodType.methodType(void.class)); + { + MethodHandle booleanMH = voidMH.asType(MethodType.methodType(boolean.class)); + assertEquals(boolean.class, booleanMH.type().returnType()); + assertEquals(false, booleanMH.invoke()); + } + { + MethodHandle intMH = voidMH.asType(MethodType.methodType(int.class)); + assertEquals(int.class, intMH.type().returnType()); + assertEquals(0, intMH.invoke()); + } + { + MethodHandle longMH = voidMH.asType(MethodType.methodType(long.class)); + assertEquals(long.class, longMH.type().returnType()); + assertEquals(0L, longMH.invoke()); + } + { + MethodHandle objMH = voidMH.asType(MethodType.methodType(Object.class)); + assertEquals(Object.class, objMH.type().returnType()); + assertEquals(null, objMH.invoke()); + } } public static void assertTrue(boolean value) { @@ -755,6 +785,14 @@ public class Main { Object[].class, MethodType.methodType(void.class)); fail("Unexpected success for array class type for findConstructor"); } catch (NoSuchMethodException e) {} + + // Child class constructor (b/143343351) + { + MethodHandle handle = MethodHandles.lookup().findConstructor( + ArrayList.class, MethodType.methodType(void.class)); + AbstractList list = (AbstractList) handle.asType(MethodType.methodType(AbstractList.class)) + .invokeExact(); + } } public static void testStringConstructors() throws Throwable { @@ -874,6 +912,16 @@ public class Main { fail("Unexpected string constructor result: '" + s + "'"); } + // Child class constructor (b/143343351) + { + MethodHandle handle = MethodHandles.lookup().findConstructor( + String.class, MethodType.methodType(void.class)); + CharSequence o = (CharSequence) handle.asType(MethodType.methodType(CharSequence.class)) + .invokeExact(); + if (!o.equals("")) { + fail("Unexpected child class constructor result: '" + o + "'"); + } + } System.out.println("String constructors done."); } diff --git a/test/Android.bp b/test/Android.bp index ac83a5dc15..f10d3788bf 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -14,6 +14,8 @@ // limitations under the License. // +// ART gtests. + art_cc_defaults { name: "art_test_defaults", host_supported: true, @@ -168,6 +170,62 @@ art_cc_library { }, } +// ART run-tests. + +art_cc_test_library { + name: "libarttest", + defaults: ["libarttest-defaults"], + shared_libs: [ + "libart", + "libdexfile", + "libprofile", + "libartbase", + ], +} + +art_cc_test_library { + name: "libarttestd", + defaults: [ + "art_debug_defaults", + "libarttest-defaults", + ], + shared_libs: [ + "libartd", + "libdexfiled", + "libprofiled", + "libartbased", + ], +} + +art_cc_defaults { + name: "libnativebridgetest-defaults", + defaults: [ + "art_test_defaults", + "art_defaults", + ], + header_libs: ["libnativebridge-headers"], + srcs: ["115-native-bridge/nativebridge.cc"], +} + +art_cc_test_library { + name: "libnativebridgetest", + shared_libs: ["libart"], + defaults: [ + "libnativebridgetest-defaults", + ], +} + +art_cc_test_library { + name: "libnativebridgetestd", + shared_libs: ["libartd"], + defaults: [ + "libnativebridgetest-defaults", + "art_debug_defaults", + ], +} + +// ART JVMTI run-tests. + cc_defaults { name: "libartagent-defaults", defaults: [ @@ -529,58 +587,6 @@ cc_defaults { ], } -art_cc_test_library { - name: "libarttest", - defaults: ["libarttest-defaults"], - shared_libs: [ - "libart", - "libdexfile", - "libprofile", - "libartbase", - ], -} - -art_cc_test_library { - name: "libarttestd", - defaults: [ - "art_debug_defaults", - "libarttest-defaults", - ], - shared_libs: [ - "libartd", - "libdexfiled", - "libprofiled", - "libartbased", - ], -} - -art_cc_defaults { - name: "libnativebridgetest-defaults", - defaults: [ - "art_test_defaults", - "art_defaults", - ], - header_libs: ["libnativebridge-headers"], - srcs: ["115-native-bridge/nativebridge.cc"], -} - -art_cc_test_library { - name: "libnativebridgetest", - shared_libs: ["libart"], - defaults: [ - "libnativebridgetest-defaults", - ], -} - -art_cc_test_library { - name: "libnativebridgetestd", - shared_libs: ["libartd"], - defaults: [ - "libnativebridgetest-defaults", - "art_debug_defaults", - ], -} - filegroup { name: "art_cts_jvmti_test_library", visibility: [ diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index 5fa7e19eda..4ca5fe8333 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -32,6 +32,7 @@ #include "mirror/class-inl.h" #include "mirror/class.h" #include "nativehelper/ScopedUtfChars.h" +#include "oat.h" #include "oat_file.h" #include "oat_quick_method_header.h" #include "profile/profile_compilation_info.h" diff --git a/test/jvmti-common/Redefinition.java b/test/jvmti-common/Redefinition.java index 2ebce17686..3402fa12b5 100644 --- a/test/jvmti-common/Redefinition.java +++ b/test/jvmti-common/Redefinition.java @@ -19,7 +19,7 @@ package art; import java.util.ArrayList; // Common Redefinition functions. Placed here for use by CTS public class Redefinition { - public static final class CommonClassDefinition { + public static class CommonClassDefinition { public final Class<?> target; public final byte[] class_file_bytes; public final byte[] dex_file_bytes; @@ -31,12 +31,19 @@ public class Redefinition { } } + public static class DexOnlyClassDefinition extends CommonClassDefinition { + public DexOnlyClassDefinition(Class<?> target, byte[] dex_file_bytes) { + super(target, new byte[0], dex_file_bytes); + } + } + // A set of possible test configurations. Test should set this if they need to. // This must be kept in sync with the defines in ti-agent/common_helper.cc public static enum Config { COMMON_REDEFINE(0), COMMON_RETRANSFORM(1), - COMMON_TRANSFORM(2); + COMMON_TRANSFORM(2), + STRUCTURAL_TRANSFORM(3); private final int val; private Config(int val) { @@ -90,5 +97,18 @@ public class Redefinition { byte[] dex_bytes); public static native void doCommonStructuralClassRedefinition(Class<?> target, byte[] dex_file); + public static void doMultiStructuralClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiStructuralClassRedefinition(classes.toArray(new Class<?>[0]), + dex_files.toArray(new byte[0][])); + } + public static native void doCommonMultiStructuralClassRedefinition(Class<?>[] targets, + byte[][] dexfiles); public static native boolean isStructurallyModifiable(Class<?> target); } diff --git a/test/knownfailures.json b/test/knownfailures.json index 1e00c3ff6a..cd66472aa8 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1139,7 +1139,15 @@ "1984-structural-redefine-field-trace", "1985-structural-redefine-stack-scope", "1986-structural-redefine-multi-thread-stack-scope", - "1987-structural-redefine-recursive-stack-scope" + "1987-structural-redefine-recursive-stack-scope", + "1988-multi-structural-redefine", + "1989-transform-bad-monitor", + "1990-structural-bad-verify", + "1991-hello-structural-retransform", + "1992-retransform-no-such-field", + "1993-fallback-non-structural", + "1997-structural-shadow-method", + "1998-structural-shadow-field" ], "variant": "jvm", "description": ["Doesn't run on RI."] @@ -1261,5 +1269,11 @@ "env_vars": {"ART_READ_BARRIER_TYPE": "TABLELOOKUP"}, "bug": "b/140507091", "description": ["Test containing Checker assertions expecting Baker read barriers."] + }, + { + "tests": ["689-zygote-jit-deopt"], + "variant": "gcstress", + "bug": "b/137887811", + "description": ["Occasional timeouts."] } ] diff --git a/test/run-test b/test/run-test index eeeefbb4c1..72e7562380 100755 --- a/test/run-test +++ b/test/run-test @@ -672,12 +672,12 @@ if [ "$runtime" = "dalvik" ]; then elif [ "$runtime" = "art" ]; then if [ "$target_mode" = "no" ]; then guess_host_arch_name - run_args+=(--boot "${ANDROID_HOST_OUT}/framework/core${image_suffix}.art") + run_args+=(--boot "${ANDROID_HOST_OUT}/framework/core${image_suffix}.art:*") run_args+=(--runtime-option "-Djava.library.path=${host_lib_root}/lib${suffix64}:${host_lib_root}/nativetest${suffix64}") else guess_target_arch_name run_args+=(--runtime-option "-Djava.library.path=/data/nativetest${suffix64}/art/${target_arch_name}") - run_args+=(--boot "/data/art-test/core${image_suffix}.art") + run_args+=(--boot "/data/art-test/core${image_suffix}.art:/data/art-test/*") fi if [ "$relocate" = "yes" ]; then run_args+=(--relocate) diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc index ae1a8d34f3..22bc64ac01 100644 --- a/test/ti-agent/jvmti_helper.cc +++ b/test/ti-agent/jvmti_helper.cc @@ -238,6 +238,26 @@ void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) { } } +jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name) { + jint n_ext = 0; + jint res = -1; + bool found_res = false; + jvmtiExtensionEventInfo* infos = nullptr; + CHECK_EQ(jvmti->GetExtensionEvents(&n_ext, &infos), JVMTI_ERROR_NONE); + for (jint i = 0; i < n_ext; i++) { + const jvmtiExtensionEventInfo& info = infos[i]; + if (name == info.id) { + res = info.extension_event_index; + found_res = true; + } + DeallocParams(jvmti, info.params, info.param_count); + Dealloc(jvmti, info.short_description, info.id, info.params); + } + Dealloc(jvmti, infos); + CHECK(found_res); + return res; +} + void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) { jint n_ext = 0; void* res = nullptr; diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h index a3b95353c7..74d594ff04 100644 --- a/test/ti-agent/jvmti_helper.h +++ b/test/ti-agent/jvmti_helper.h @@ -94,6 +94,8 @@ template<typename T> T GetExtensionFunction(JNIEnv* env, jvmtiEnv* jvmti, const return reinterpret_cast<T>(GetExtensionFunctionVoid(env, jvmti, name)); } +jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name); + } // namespace art #endif // ART_TEST_TI_AGENT_JVMTI_HELPER_H_ diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc index 9d9f13fa39..0baa9fe547 100644 --- a/test/ti-agent/redefinition_helper.cc +++ b/test/ti-agent/redefinition_helper.cc @@ -31,8 +31,13 @@ namespace art { +enum class RedefineType { + kNormal, + kStructural, +}; + static void SetupCommonRedefine(); -static void SetupCommonRetransform(); +static void SetupCommonRetransform(RedefineType type); static void SetupCommonTransform(); template <bool is_redefine> static void throwCommonRedefinitionError(jvmtiEnv* jvmti, @@ -68,6 +73,7 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti, #define CONFIGURATION_COMMON_REDEFINE 0 #define CONFIGURATION_COMMON_RETRANSFORM 1 #define CONFIGURATION_COMMON_TRANSFORM 2 +#define CONFIGURATION_STRUCTURAL_TRANSFORM 3 extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*, jclass, @@ -78,21 +84,54 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfigurati return; } case CONFIGURATION_COMMON_RETRANSFORM: { - SetupCommonRetransform(); + SetupCommonRetransform(RedefineType::kNormal); return; } case CONFIGURATION_COMMON_TRANSFORM: { SetupCommonTransform(); return; } + case CONFIGURATION_STRUCTURAL_TRANSFORM: { + SetupCommonRetransform(RedefineType::kStructural); + return; + } default: { LOG(FATAL) << "Unknown test configuration: " << type; } } } +template<RedefineType kType> +static bool SupportsAndIsJVM() { + if constexpr (kType == RedefineType::kStructural) { + return false; + } else { + return IsJVM(); + } +} + + namespace common_redefine { +template <RedefineType kType> +static jvmtiError CallRedefineEntrypoint(JNIEnv* env, + jvmtiEnv* jvmti, + jint num_defs, + const jvmtiClassDefinition* defs) { + decltype(jvmti->functions->RedefineClasses) entrypoint = nullptr; + if constexpr (kType == RedefineType::kNormal) { + entrypoint = jvmti->functions->RedefineClasses; + } else { + entrypoint = GetExtensionFunction<decltype(entrypoint)>( + env, jvmti_env, "com.android.art.class.structurally_redefine_classes"); + } + if (entrypoint == nullptr) { + LOG(INFO) << "Could not find entrypoint!"; + return JVMTI_ERROR_NOT_AVAILABLE; + } + return entrypoint(jvmti, num_defs, defs); +} + static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jint num_targets, @@ -101,6 +140,7 @@ static void throwRedefinitionError(jvmtiEnv* jvmti, return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); } +template<RedefineType kType> static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, JNIEnv* env, jint num_redefines, @@ -109,24 +149,25 @@ static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, jbyteArray* dex_file_bytes) { std::vector<jvmtiClassDefinition> defs; for (jint i = 0; i < num_redefines; i++) { - jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; + jbyteArray desired_array = SupportsAndIsJVM<kType>() ? class_file_bytes[i] : dex_file_bytes[i]; jint len = static_cast<jint>(env->GetArrayLength(desired_array)); const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( env->GetByteArrayElements(desired_array, nullptr)); defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); } - jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); + jvmtiError res = CallRedefineEntrypoint<kType>(env, jvmti_env, num_redefines, defs.data()); if (res != JVMTI_ERROR_NONE) { throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); } } +template<RedefineType kType> static void DoClassRedefine(jvmtiEnv* jvmti_env, JNIEnv* env, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { - return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); + return DoMultiClassRedefine<kType>(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); } extern "C" JNIEXPORT jboolean JNICALL @@ -145,25 +186,44 @@ Java_art_Redefinition_isStructurallyModifiable(JNIEnv* env, jclass, jclass targe extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonStructuralClassRedefinition( JNIEnv* env, jclass, jclass target, jbyteArray dex_file_bytes) { - using ArtStructurallyRedefineClassDirect = - jvmtiError (*)(jvmtiEnv * env, jclass k, jbyte* data, jint len); - ArtStructurallyRedefineClassDirect redef = - GetExtensionFunction<ArtStructurallyRedefineClassDirect>( - env, jvmti_env, "com.android.art.UNSAFE.class.structurally_redefine_class_direct"); - if (redef == nullptr || env->ExceptionCheck()) { - return; - } - jint len = env->GetArrayLength(dex_file_bytes); - std::vector<jbyte> v(len, 0); - env->GetByteArrayRegion(dex_file_bytes, 0, len, v.data()); - JvmtiErrorToException(env, jvmti_env, redef(jvmti_env, target, v.data(), len)); + DoClassRedefine<RedefineType::kStructural>(jvmti_env, env, target, nullptr, dex_file_bytes); } // Magic JNI export that classes can use for redefining classes. // To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition( JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { - DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); + DoClassRedefine<RedefineType::kNormal>(jvmti_env, env, target, class_file_bytes, dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature +// ([Ljava/lang/Class;[[B[[B)V +extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiStructuralClassRedefinition( + JNIEnv* env, + jclass, + jobjectArray targets, + jobjectArray dex_file_bytes) { + std::vector<jclass> classes; + std::vector<jbyteArray> class_files; + std::vector<jbyteArray> dex_files; + jint len = env->GetArrayLength(targets); + if (len != env->GetArrayLength(dex_file_bytes)) { + env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), + "the three array arguments passed to this function have different lengths!"); + return; + } + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); + class_files.push_back(nullptr); + } + return DoMultiClassRedefine<RedefineType::kStructural>(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); } // Magic JNI export that classes can use for redefining classes. @@ -189,12 +249,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefi dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); } - return DoMultiClassRedefine(jvmti_env, - env, - len, - classes.data(), - class_files.data(), - dex_files.data()); + return DoMultiClassRedefine<RedefineType::kNormal>(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); } // Get all capabilities except those related to retransformation. @@ -380,7 +440,7 @@ jint OnLoad(JavaVM* vm, printf("Unable to get jvmti env!\n"); return 1; } - SetupCommonRetransform(); + SetupCommonRetransform(RedefineType::kNormal); return 0; } @@ -409,11 +469,20 @@ static void SetupCommonRedefine() { jvmti_env->AddCapabilities(&caps); } -static void SetupCommonRetransform() { +static void SetupCommonRetransform(RedefineType type) { SetStandardCapabilities(jvmti_env); - current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); - CHECK_EQ(res, JVMTI_ERROR_NONE); + if (type == RedefineType::kNormal) { + current_callbacks.ClassFileLoadHook = + common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + } else { + jvmtiError res = jvmti_env->SetExtensionEventCallback( + GetExtensionEventId(jvmti_env, "com.android.art.class.structural_dex_file_load_hook"), + reinterpret_cast<jvmtiExtensionEvent>( + common_retransform::CommonClassFileLoadHookRetransformable)); + CHECK_EQ(res, JVMTI_ERROR_NONE); + } common_retransform::gTransformations.clear(); } diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py index 5639617e2e..b19c2920f0 100755 --- a/tools/generate_cmake_lists.py +++ b/tools/generate_cmake_lists.py @@ -68,7 +68,7 @@ def main(): ANDROID_BUILD_TOP = get_android_build_top() - subprocess.check_output('m -j64', shell=True, cwd=ANDROID_BUILD_TOP) + subprocess.check_output('build/soong/soong_ui.bash --make-mode', shell=True, cwd=ANDROID_BUILD_TOP) out_art_cmakelists_dir = os.path.join(ANDROID_BUILD_TOP, 'out/development/ide/clion/art') diff --git a/tools/jvmti-agents/ti-alloc-sample/Android.bp b/tools/jvmti-agents/ti-alloc-sample/Android.bp new file mode 100644 index 0000000000..0dc2dd8fb0 --- /dev/null +++ b/tools/jvmti-agents/ti-alloc-sample/Android.bp @@ -0,0 +1,73 @@ +// +// Copyright (C) 2018 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. +// + +// Build variants {target,host} x {debug,ndebug} x {32,64} +cc_defaults { + name: "ti-alloc-sample-base-defaults", + srcs: ["ti_alloc_sample.cc"], + defaults: ["art_defaults"], + + // Note that this tool needs to be built for both 32-bit and 64-bit since it requires + // to be same ISA as what it is attached to. + compile_multilib: "both", + header_libs: [ + "libopenjdkjvmti_headers", + "libnativehelper_header_only", + "jni_headers", + ], +} + +cc_defaults { + name: "ti-alloc-sample-defaults", + host_supported: true, + shared_libs: [ + "libbase", + ], + defaults: ["ti-alloc-sample-base-defaults"], +} + +cc_defaults { + name: "ti-alloc-sample-static-defaults", + host_supported: false, + defaults: ["ti-alloc-sample-base-defaults"], + + shared_libs: [ + "liblog", + ], + static_libs: [ + "libbase_ndk", + ], + sdk_version: "current", + stl: "c++_static", +} + +art_cc_library { + name: "libtiallocsamples", + defaults: ["ti-alloc-sample-static-defaults"], +} + +art_cc_library { + name: "libtiallocsample", + defaults: ["ti-alloc-sample-defaults"], +} + +art_cc_library { + name: "libtiallocsampled", + defaults: [ + "art_debug_defaults", + "ti-alloc-sample-defaults", + ], +} diff --git a/tools/jvmti-agents/ti-alloc-sample/README.md b/tools/jvmti-agents/ti-alloc-sample/README.md new file mode 100644 index 0000000000..0da090af12 --- /dev/null +++ b/tools/jvmti-agents/ti-alloc-sample/README.md @@ -0,0 +1,79 @@ +# tiallocsample + +tiallocsample is a JVMTI agent designed to track the call stacks of allocations +in the heap. + +# Usage +### Build +> `m libtiallocsample` + +The libraries will be built for 32-bit, 64-bit, host and target. Below examples +assume you want to use the 64-bit version. + +Use `libtiallocsamples` if you wish to build a version without non-NDK dynamic dependencies. + +### Command Line + +The agent is loaded using -agentpath like normal. It takes arguments in the +following format: +> `sample_rate,stack_depth_limit,log_path` + +* sample_rate is an integer specifying how frequently an event is reported. + E.g., 10 means every tenth call to new will be logged. +* stack_depth_limit is an integer that determines the number of frames the deepest stack trace + can contain. It returns just the top portion if the limit is exceeded. +* log_path is an absolute file path specifying where the log is to be written. + +#### Output Format + +The resulting file is a sequence of object allocations, with a limited form of +text compression. For example a single stack frame might look like: + +``` ++0,jthread[main], jclass[[I file: <UNKNOWN_FILE>], size[24, hex: 0x18] ++1,main([Ljava/lang/String;)V ++2,run()V ++3,invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; ++4,loop()V ++5,dispatchMessage(Landroid/os/Message;)V ++6,handleMessage(Landroid/os/Message;)V ++7,onDisplayChanged(I)V ++8,getState()I ++9,updateDisplayInfoLocked()V ++10,getDisplayInfo(I)Landroid/view/DisplayInfo; ++11,createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object; ++12,createFromParcel(Landroid/os/Parcel;)Landroid/view/DisplayInfo; ++13,<init>(Landroid/os/Parcel;Landroid/view/DisplayInfo$1;)V ++14,<init>(Landroid/os/Parcel;)V ++15,readFromParcel(Landroid/os/Parcel;)V +=16,0;1;2;3;1;4;5;6;7;8;9;10;10;11;12;13;14;15 +16 +``` + +Lines starting with a + are key, value pairs. So, for instance, key 2 stands for +``` +run()V +``` +. + +The line starting with 0 is the thread, type, and size (TTS) of an allocation. The +remaining lines starting with + are stack frames (SFs), containing function signatures. +Lines starting with = are stack traces (STs), and are again key, value pairs. In the +example above, an ST called 16 is the TTS plus sequence of SFs. Any line not starting +with + or = is a sample. It is a reference to an ST. Hence repeated samples are +represented as just numbers. + +#### ART +> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so '-agentpath:libtiallocsample.so=100' -cp tmp/java/helloworld.dex -Xint helloworld` + +* `-Xplugin` and `-agentpath` need to be used, otherwise the agent will fail during init. +* If using `libartd.so`, make sure to use the debug version of jvmti. + +> `adb shell setenforce 0` +> +> `adb push $ANDROID_PRODUCT_OUT/system/lib64/libtiallocsample.so /data/local/tmp/` +> +> `adb shell am start-activity --attach-agent /data/local/tmp/libtiallocsample.so=100 some.debuggable.apps/.the.app.MainActivity` + +#### RI +> `java '-agentpath:libtiallocsample.so=MethodEntry' -cp tmp/helloworld/classes helloworld` diff --git a/tools/jvmti-agents/ti-alloc-sample/mkflame.py b/tools/jvmti-agents/ti-alloc-sample/mkflame.py new file mode 100755 index 0000000000..f37aa4aafb --- /dev/null +++ b/tools/jvmti-agents/ti-alloc-sample/mkflame.py @@ -0,0 +1,213 @@ +#!/usr/bin/python3 +# +# Copyright 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. + +""" +Usage: mkflame.py <jvmti_trace_file> +""" + +import argparse +import sys + +class TraceCollection: + def __init__(self, args): + self.args = args + # A table indexed by number and containing the definition for that number. + self.definitions = {} + # The "weight" of a stack trace, either 1 for counting or the size of the allocation. + self.weights = {} + # The count for each individual allocation. + self.allocation_count = {} + + def definition(self, index): + """ + Returns the definition for "index". + """ + return self.definitions[index] + + def set_definition(self, index, definition): + """ + Sets the definition for "index". + """ + self.definitions[index] = definition + + def weight(self, index): + """ + Returns the weight for "index". + """ + return self.weights[index] + + def set_weight(self, index, weight): + """ + Sets the weight for "index". + """ + self.weights[index] = weight + + def read_file(self, filename): + """ + Reads a file into a DefinitionTable. + """ + def process_definition(line): + """ + Adds line to the list of definitions in table. + """ + def expand_stack_trace(definition): + """ + Converts a semicolon-separated list of numbers into the text stack trace. + """ + def get_allocation_thread(thread_type_size): + """ + Returns the thread of an allocation from the thread/type/size record. + """ + THREAD_STRING = "thread[" + THREAD_STRING_LEN = len(THREAD_STRING) + thread_string = thread_type_size[thread_type_size.find(THREAD_STRING) + + THREAD_STRING_LEN:] + return thread_string[:thread_string.find("]")] + + def get_allocation_type(thread_type_size): + """ + Returns the type of an allocation from the thread/type/size record. + """ + TYPE_STRING = "jclass[" + TYPE_STRING_LEN = len(TYPE_STRING) + type_string = thread_type_size[thread_type_size.find(TYPE_STRING) + TYPE_STRING_LEN:] + return type_string[:type_string.find(" ")] + + def get_allocation_size(thread_type_size): + """ + Returns the size of an allocation from the thread/type/size record. + """ + SIZE_STRING = "size[" + SIZE_STRING_LEN = len(SIZE_STRING) + size_string = thread_type_size[thread_type_size.find(SIZE_STRING) + SIZE_STRING_LEN:] + size_string = size_string[:size_string.find(",")] + return int(size_string) + + def get_top_and_weight(index): + thread_type_size = self.definition(int(tokens[0])) + size = get_allocation_size(thread_type_size) + if self.args.type_only: + thread_type_size = get_allocation_type(thread_type_size) + elif self.args.thread_only: + thread_type_size = get_allocation_thread(thread_type_size) + return (thread_type_size, size) + + tokens = definition.split(";") + # The first element (base) of the stack trace is the thread/type/size. + # Get the weight (either 1 or the number of bytes allocated). + (thread_type_size, weight) = get_top_and_weight(int(tokens[0])) + self.set_weight(index, weight) + # Remove the thread/type/size from the base of the stack trace. + del tokens[0] + # Build the stack trace list. + expanded_definition = "" + for i in range(len(tokens)): + if self.args.depth_limit > 0 and i >= self.args.depth_limit: + break + token = tokens[i] + # Replace semicolons by colons in the method entry signatures. + method = self.definition(int(token)).replace(";", ":") + if len(expanded_definition) > 0: + expanded_definition += ";" + expanded_definition += method + if not self.args.ignore_type: + # Add the thread/type/size as the top-most stack frame. + if len(expanded_definition) > 0: + expanded_definition += ";" + expanded_definition += thread_type_size.replace(";", ":") + if self.args.reverse_stack: + def_list = expanded_definition.split(";") + expanded_definition = ";".join(def_list[::-1]) + return expanded_definition + + # If the line contains a comma, it is of the form [+=]index,definition, + # where index is a string containing an integer, and definition is the + # value represented by the integer whenever it is used later. + # * Lines starting with + are either a thread/type/size record or a single + # stack frame. These are simply interned in the table. + # * Those starting with = are stack traces, and contain a sequence of + # numbers separated by semicolon. These are "expanded" and then interned. + comma_pos = line.find(",") + index = int(line[1:comma_pos]) + definition = line[comma_pos+1:] + if line[0:1] == "=": + definition = expand_stack_trace(definition) + # Intern the definition in the table. + #if len(definition) == 0: + # Zero length samples are errors and are discarded. + #print("ERROR: definition for " + str(index) + " is empty") + #return + self.set_definition(index, definition) + + def process_trace(index): + """ + Remembers one stack trace in the list of stack traces we have seen. + Remembering a stack trace increments a count associated with the trace. + """ + trace = self.definition(index) + if self.args.use_size: + weight = self.weight(index) + else: + weight = 1 + if trace in self.allocation_count: + self.allocation_count[trace] = self.allocation_count[trace] + weight + else: + self.allocation_count[trace] = weight + + # Read the file, processing each line as a definition or stack trace. + tracefile = open(filename, "r") + current_allocation_trace = "" + for line in tracefile: + line = line.rstrip("\n") + if line[0:1] == "=" or line[0:1] == "+": + # definition. + process_definition(line) + else: + # stack trace. + process_trace(int(line)) + + def dump_flame_graph(self): + """ + Prints out a stack trace format compatible with flame graph creation utilities. + """ + for definition, weight in self.allocation_count.items(): + print(definition + " " + str(weight)) + +def parse_options(): + parser = argparse.ArgumentParser(description="Convert a trace to a form usable for flame graphs.") + parser.add_argument("filename", help="The trace file as input", type=str) + parser.add_argument("--use_size", help="Count by allocation size", action="store_true", + default=False) + parser.add_argument("--ignore_type", help="Ignore type of allocation", action="store_true", + default=False) + parser.add_argument("--reverse_stack", help="Reverse root and top of stacks", action="store_true", + default=False) + parser.add_argument("--type_only", help="Only consider allocation type", action="store_true", + default=False) + parser.add_argument("--thread_only", help="Only consider allocation thread", action="store_true", + default=False) + parser.add_argument("--depth_limit", help="Limit the length of a trace", type=int, default=0) + args = parser.parse_args() + return args + +def main(argv): + args = parse_options() + trace_collection = TraceCollection(args) + trace_collection.read_file(args.filename) + trace_collection.dump_flame_graph() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/jvmti-agents/ti-alloc-sample/ti_alloc_sample.cc b/tools/jvmti-agents/ti-alloc-sample/ti_alloc_sample.cc new file mode 100644 index 0000000000..d719db5af8 --- /dev/null +++ b/tools/jvmti-agents/ti-alloc-sample/ti_alloc_sample.cc @@ -0,0 +1,461 @@ +// Copyright (C) 2018 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 <android-base/logging.h> + +#include <atomic> +#include <fstream> +#include <iostream> +#include <istream> +#include <iomanip> +#include <jni.h> +#include <jvmti.h> +#include <limits> +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <sstream> +#include <vector> + +namespace tifast { + +namespace { + +// Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI +// env. +static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000; + +// jthread is a typedef of jobject so we use this to allow the templates to distinguish them. +struct jthreadContainer { jthread thread; }; +// jlocation is a typedef of jlong so use this to distinguish the less common jlong. +struct jlongContainer { jlong val; }; + +static void DeleteLocalRef(JNIEnv* env, jobject obj) { + if (obj != nullptr && env != nullptr) { + env->DeleteLocalRef(obj); + } +} + +class ScopedThreadInfo { + public: + ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread) + : jvmtienv_(jvmtienv), env_(env), free_name_(false) { + if (thread == nullptr) { + info_.name = const_cast<char*>("<NULLPTR>"); + } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) { + info_.name = const_cast<char*>("<UNKNOWN THREAD>"); + } else { + free_name_ = true; + } + } + + ~ScopedThreadInfo() { + if (free_name_) { + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name)); + } + DeleteLocalRef(env_, info_.thread_group); + DeleteLocalRef(env_, info_.context_class_loader); + } + + const char* GetName() const { + return info_.name; + } + + private: + jvmtiEnv* jvmtienv_; + JNIEnv* env_; + bool free_name_; + jvmtiThreadInfo info_{}; +}; + +class ScopedClassInfo { + public: + ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c) : jvmtienv_(jvmtienv), class_(c) {} + + ~ScopedClassInfo() { + if (class_ != nullptr) { + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_)); + } + } + + bool Init(bool get_generic = true) { + if (class_ == nullptr) { + name_ = const_cast<char*>("<NONE>"); + generic_ = const_cast<char*>("<NONE>"); + return true; + } else { + jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_); + jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_); + char** gen_ptr = &generic_; + if (!get_generic) { + generic_ = nullptr; + gen_ptr = nullptr; + } + return jvmtienv_->GetClassSignature(class_, &name_, gen_ptr) == JVMTI_ERROR_NONE && + ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && + ret1 != JVMTI_ERROR_INVALID_CLASS && + ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && + ret2 != JVMTI_ERROR_INVALID_CLASS; + } + } + + jclass GetClass() const { + return class_; + } + + const char* GetName() const { + return name_; + } + + const char* GetGeneric() const { + return generic_; + } + + const char* GetSourceDebugExtension() const { + if (debug_ext_ == nullptr) { + return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>"; + } else { + return debug_ext_; + } + } + const char* GetSourceFileName() const { + if (file_ == nullptr) { + return "<UNKNOWN_FILE>"; + } else { + return file_; + } + } + + private: + jvmtiEnv* jvmtienv_; + jclass class_; + char* name_ = nullptr; + char* generic_ = nullptr; + char* file_ = nullptr; + char* debug_ext_ = nullptr; + + friend std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& m); +}; + +class ScopedMethodInfo { + public: + ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m) + : jvmtienv_(jvmtienv), env_(env), method_(m) {} + + ~ScopedMethodInfo() { + DeleteLocalRef(env_, declaring_class_); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_)); + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); + } + + bool Init(bool get_generic = true) { + if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) { + return false; + } + class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); + jint nlines; + jvmtiLineNumberEntry* lines; + jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines); + if (err == JVMTI_ERROR_NONE) { + if (nlines > 0) { + first_line_ = lines[0].line_number; + } + jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines)); + } else if (err != JVMTI_ERROR_ABSENT_INFORMATION && + err != JVMTI_ERROR_NATIVE_METHOD) { + return false; + } + return class_info_->Init(get_generic) && + (jvmtienv_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE); + } + + const ScopedClassInfo& GetDeclaringClassInfo() const { + return *class_info_; + } + + jclass GetDeclaringClass() const { + return declaring_class_; + } + + const char* GetName() const { + return name_; + } + + const char* GetSignature() const { + return signature_; + } + + const char* GetGeneric() const { + return generic_; + } + + jint GetFirstLine() const { + return first_line_; + } + + private: + jvmtiEnv* jvmtienv_; + JNIEnv* env_; + jmethodID method_; + jclass declaring_class_ = nullptr; + std::unique_ptr<ScopedClassInfo> class_info_; + char* name_ = nullptr; + char* signature_ = nullptr; + char* generic_ = nullptr; + jint first_line_ = -1; +}; + +std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& c) { + const char* generic = c.GetGeneric(); + if (generic != nullptr) { + return os << c.GetName() << "<" << generic << ">" << " file: " << c.GetSourceFileName(); + } else { + return os << c.GetName() << " file: " << c.GetSourceFileName(); + } +} + +class LockedStream { + public: + explicit LockedStream(const std::string& filepath) { + stream_.open(filepath, std::ofstream::out); + if (!stream_.is_open()) { + LOG(ERROR) << "====== JVMTI FAILED TO OPEN LOG FILE"; + } + } + ~LockedStream() { + stream_.close(); + } + void Write(const std::string& str) { + stream_ << str; + stream_.flush(); + } + private: + std::ofstream stream_; +}; + +static LockedStream* stream = nullptr; + +class UniqueStringTable { + public: + UniqueStringTable() = default; + ~UniqueStringTable() = default; + std::string Intern(const std::string& header, const std::string& key) { + if (map_.find(key) == map_.end()) { + map_[key] = next_index_; + // Emit definition line. E.g., =123,string + stream->Write(header + std::to_string(next_index_) + "," + key + "\n"); + ++next_index_; + } + return std::to_string(map_[key]); + } + private: + int32_t next_index_; + std::map<std::string, int32_t> map_; +}; + +static UniqueStringTable* string_table = nullptr; + +// Formatter for the thread, type, and size of an allocation. +static std::string formatAllocation(jvmtiEnv* jvmti, + JNIEnv* jni, + jthreadContainer thr, + jclass klass, + jlongContainer size) { + ScopedThreadInfo sti(jvmti, jni, thr.thread); + std::ostringstream allocation; + allocation << "jthread[" << sti.GetName() << "]"; + ScopedClassInfo sci(jvmti, klass); + if (sci.Init(/*get_generic=*/false)) { + allocation << ", jclass[" << sci << "]"; + } else { + allocation << ", jclass[TYPE UNKNOWN]"; + } + allocation << ", size[" << size.val << ", hex: 0x" << std::hex << size.val << "]"; + return string_table->Intern("+", allocation.str()); +} + +// Formatter for a method entry on a call stack. +static std::string formatMethod(jvmtiEnv* jvmti, JNIEnv* jni, jmethodID method_id) { + ScopedMethodInfo smi(jvmti, jni, method_id); + std::string method; + if (smi.Init(/*get_generic=*/false)) { + method = std::string(smi.GetDeclaringClassInfo().GetName()) + + "::" + smi.GetName() + smi.GetSignature(); + } else { + method = "ERROR"; + } + return string_table->Intern("+", method); +} + +static int sampling_rate; +static int stack_depth_limit; + +static void JNICALL logVMObjectAlloc(jvmtiEnv* jvmti, + JNIEnv* jni, + jthread thread, + jobject obj ATTRIBUTE_UNUSED, + jclass klass, + jlong size) { + // Sample only once out of sampling_rate tries, and prevent recursive allocation tracking, + static thread_local int sample_countdown = sampling_rate; + --sample_countdown; + if (sample_countdown != 0) { + return; + } + + // Guard accesses to string table and emission. + static std::mutex mutex; + std::lock_guard<std::mutex> lg(mutex); + + std::string record = + formatAllocation(jvmti, + jni, + jthreadContainer{.thread = thread}, + klass, + jlongContainer{.val = size}); + + std::unique_ptr<jvmtiFrameInfo[]> stack_frames(new jvmtiFrameInfo[stack_depth_limit]); + jint stack_depth; + jvmtiError err = jvmti->GetStackTrace(thread, + 0, + stack_depth_limit, + stack_frames.get(), + &stack_depth); + if (err == JVMTI_ERROR_NONE) { + // Emit stack frames in order from deepest in the stack to most recent. + // This simplifies post-collection processing. + for (int i = stack_depth - 1; i >= 0; --i) { + record += ";" + formatMethod(jvmti, jni, stack_frames[i].method); + } + } + stream->Write(string_table->Intern("=", record) + "\n"); + + sample_countdown = sampling_rate; +} + +static jvmtiEventCallbacks kLogCallbacks { + .VMObjectAlloc = logVMObjectAlloc, +}; + +static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) { + jint res = vm->GetEnv(reinterpret_cast<void**>(jvmti), JVMTI_VERSION_1_1); + if (res != JNI_OK || *jvmti == nullptr) { + LOG(ERROR) << "Unable to access JVMTI, error code " << res; + return vm->GetEnv(reinterpret_cast<void**>(jvmti), kArtTiVersion); + } + return res; +} + +} // namespace + +static jvmtiError SetupCapabilities(jvmtiEnv* jvmti) { + jvmtiCapabilities caps{}; + caps.can_generate_vm_object_alloc_events = 1; + caps.can_get_line_numbers = 1; + caps.can_get_source_file_name = 1; + caps.can_get_source_debug_extension = 1; + return jvmti->AddCapabilities(&caps); +} + +static bool ProcessOptions(std::string options) { + std::string output_file_path; + if (options.empty()) { + static constexpr int kDefaultSamplingRate = 10; + static constexpr int kDefaultStackDepthLimit = 50; + static constexpr const char* kDefaultOutputFilePath = "/data/local/tmp/logstream.txt"; + + sampling_rate = kDefaultSamplingRate; + stack_depth_limit = kDefaultStackDepthLimit; + output_file_path = kDefaultOutputFilePath; + } else { + // options string should contain "sampling_rate,stack_depth_limit,output_file_path". + size_t comma_pos = options.find(','); + if (comma_pos == std::string::npos) { + return false; + } + sampling_rate = std::stoi(options.substr(0, comma_pos)); + options = options.substr(comma_pos + 1); + comma_pos = options.find(','); + if (comma_pos == std::string::npos) { + return false; + } + stack_depth_limit = std::stoi(options.substr(0, comma_pos)); + output_file_path = options.substr(comma_pos + 1); + } + LOG(INFO) << "Starting allocation tracing: sampling_rate=" << sampling_rate + << ", stack_depth_limit=" << stack_depth_limit + << ", output_file_path=" << output_file_path; + stream = new LockedStream(output_file_path); + + return true; +} + +static jint AgentStart(JavaVM* vm, + char* options, + void* reserved ATTRIBUTE_UNUSED) { + // Handle the sampling rate, depth limit, and output path, if set. + if (!ProcessOptions(options)) { + return JNI_ERR; + } + + // Create the environment. + jvmtiEnv* jvmti = nullptr; + if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) { + LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!"; + return JNI_ERR; + } + + jvmtiError error = SetupCapabilities(jvmti); + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to set caps"; + return JNI_ERR; + } + + // Add callbacks and notification. + error = jvmti->SetEventCallbacks(&kLogCallbacks, static_cast<jint>(sizeof(kLogCallbacks))); + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to set event callbacks."; + return JNI_ERR; + } + error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_VM_OBJECT_ALLOC, + nullptr /* all threads */); + if (error != JVMTI_ERROR_NONE) { + LOG(ERROR) << "Unable to enable event " << JVMTI_EVENT_VM_OBJECT_ALLOC; + return JNI_ERR; + } + + string_table = new UniqueStringTable(); + + return JNI_OK; +} + +// Late attachment (e.g. 'am attach-agent'). +extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) { + return AgentStart(vm, options, reserved); +} + +// Early attachment +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { + return AgentStart(jvm, options, reserved); +} + +} // namespace tifast + diff --git a/tools/libcore_no_getrandom_failures.txt b/tools/libcore_no_getrandom_failures.txt index f2926e452c..05af9d7e19 100644 --- a/tools/libcore_no_getrandom_failures.txt +++ b/tools/libcore_no_getrandom_failures.txt @@ -15,6 +15,21 @@ "libcore.java.math.BigIntegerTest#test_probablePrime", "libcore.javax.crypto.CipherInputStreamTest#testDecryptCorruptGCM", "libcore.javax.crypto.CipherOutputStreamTest#testDecryptCorruptGCM", + "libcore.libcore.timezone.TelephonyLookupTest#createInstanceWithFallback", + "libcore.libcore.timezone.TelephonyLookupTest#getTelephonyNetworkFinder", + "libcore.libcore.timezone.TelephonyLookupTest#validateCountryCodeLowerCase", + "libcore.libcore.timezone.TelephonyLookupTest#validateDuplicateMccMnc", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_emptyFile", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_emptyNetworksOk", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_missingCountryCodeAttribute", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_missingMccAttribute", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_missingMncAttribute", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_missingNetworks", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_truncatedInput", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_unexpectedComments", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_unexpectedElementsIgnored", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_unexpectedRootElement", + "libcore.libcore.timezone.TelephonyLookupTest#xmlParsing_unexpectedTextIgnored", "libcore.libcore.timezone.TimeZoneFinderTest#createInstanceWithFallback", "libcore.libcore.timezone.TimeZoneFinderTest#getCountryZonesFinder", "libcore.libcore.timezone.TimeZoneFinderTest#getCountryZonesFinder_empty", |