diff options
104 files changed, 2425 insertions, 1822 deletions
diff --git a/Android.mk b/Android.mk index 19c65a1e67..7852be519f 100644 --- a/Android.mk +++ b/Android.mk @@ -41,18 +41,18 @@ endif .PHONY: clean-oat-target clean-oat-target: - adb root - adb wait-for-device remount - adb shell rm -rf $(ART_TARGET_NATIVETEST_DIR) - adb shell rm -rf $(ART_TARGET_TEST_DIR) - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*/* - adb shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$(DEX2OAT_TARGET_ARCH) - adb shell rm -rf system/app/$(DEX2OAT_TARGET_ARCH) + $(ADB) root + $(ADB) wait-for-device remount + $(ADB) shell rm -rf $(ART_TARGET_NATIVETEST_DIR) + $(ADB) shell rm -rf $(ART_TARGET_TEST_DIR) + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*/* + $(ADB) shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$(DEX2OAT_TARGET_ARCH) + $(ADB) shell rm -rf system/app/$(DEX2OAT_TARGET_ARCH) ifdef TARGET_2ND_ARCH - adb shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) - adb shell rm -rf system/app/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) + $(ADB) shell rm -rf $(DEXPREOPT_BOOT_JAR_DIR)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) + $(ADB) shell rm -rf system/app/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH) endif - adb shell rm -rf data/run-test/test-*/dalvik-cache/* + $(ADB) shell rm -rf data/run-test/test-*/dalvik-cache/* ######################################################################## # cpplint rules to style check art source files @@ -92,7 +92,7 @@ endif # test rules # All the dependencies that must be built ahead of sync-ing them onto the target device. -TEST_ART_TARGET_SYNC_DEPS := +TEST_ART_TARGET_SYNC_DEPS := $(ADB_EXECUTABLE) include $(art_path)/build/Android.common_test.mk include $(art_path)/build/Android.gtest.mk @@ -100,14 +100,14 @@ include $(art_path)/test/Android.run-test.mk # Make sure /system is writable on the device. TEST_ART_ADB_ROOT_AND_REMOUNT := \ - (adb root && \ - adb wait-for-device remount && \ - ((adb shell touch /system/testfile && \ - (adb shell rm /system/testfile || true)) || \ - (adb disable-verity && \ - adb reboot && \ - adb wait-for-device root && \ - adb wait-for-device remount))) + ($(ADB) root && \ + $(ADB) wait-for-device remount && \ + (($(ADB) shell touch /system/testfile && \ + ($(ADB) shell rm /system/testfile || true)) || \ + ($(ADB) disable-verity && \ + $(ADB) reboot && \ + $(ADB) wait-for-device root && \ + $(ADB) wait-for-device remount))) # Sync test files to the target, depends upon all things that must be pushed to the target. .PHONY: test-art-target-sync @@ -121,25 +121,25 @@ ifeq ($(ART_TEST_ANDROID_ROOT),) ifeq ($(ART_TEST_CHROOT),) test-art-target-sync: $(TEST_ART_TARGET_SYNC_DEPS) $(TEST_ART_ADB_ROOT_AND_REMOUNT) - adb sync system && adb sync data + $(ADB) sync system && $(ADB) sync data else # TEST_ART_ADB_ROOT_AND_REMOUNT is not needed here, as we are only # pushing things to the chroot dir, which is expected to be under # /data on the device. test-art-target-sync: $(TEST_ART_TARGET_SYNC_DEPS) - adb wait-for-device - adb push $(PRODUCT_OUT)/system $(ART_TEST_CHROOT)/ - adb push $(PRODUCT_OUT)/data $(ART_TEST_CHROOT)/ + $(ADB) wait-for-device + $(ADB) push $(PRODUCT_OUT)/system $(ART_TEST_CHROOT)/ + $(ADB) push $(PRODUCT_OUT)/data $(ART_TEST_CHROOT)/ endif else test-art-target-sync: $(TEST_ART_TARGET_SYNC_DEPS) $(TEST_ART_ADB_ROOT_AND_REMOUNT) - adb wait-for-device - adb push $(PRODUCT_OUT)/system $(ART_TEST_CHROOT)$(ART_TEST_ANDROID_ROOT) + $(ADB) wait-for-device + $(ADB) push $(PRODUCT_OUT)/system $(ART_TEST_CHROOT)$(ART_TEST_ANDROID_ROOT) # Push the contents of the `data` dir into `$(ART_TEST_CHROOT)/data` on the device (note # that $(ART_TEST_CHROOT) can be empty). If `$(ART_TEST_CHROOT)/data` already exists on # the device, it is not overwritten, but its content is updated. - adb push $(PRODUCT_OUT)/data $(ART_TEST_CHROOT)/ + $(ADB) push $(PRODUCT_OUT)/data $(ART_TEST_CHROOT)/ endif endif @@ -493,90 +493,90 @@ build-art-target-tests: build-art-target $(TEST_ART_RUN_TEST_DEPENDENCIES) $(T .PHONY: use-art use-art: - adb root - adb wait-for-device shell stop - adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so + $(ADB) shell start .PHONY: use-artd use-artd: - adb root - adb wait-for-device shell stop - adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + $(ADB) shell start .PHONY: use-dalvik use-dalvik: - adb root - adb wait-for-device shell stop - adb shell setprop persist.sys.dalvik.vm.lib.2 libdvm.so - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libdvm.so + $(ADB) shell start .PHONY: use-art-full use-art-full: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter \"\" - adb shell setprop dalvik.vm.image-dex2oat-filter \"\" - adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so - adb shell setprop dalvik.vm.usejit false - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter \"\" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter \"\" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so + $(ADB) shell setprop dalvik.vm.usejit false + $(ADB) shell start .PHONY: use-artd-full use-artd-full: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter \"\" - adb shell setprop dalvik.vm.image-dex2oat-filter \"\" - adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so - adb shell setprop dalvik.vm.usejit false - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter \"\" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter \"\" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + $(ADB) shell setprop dalvik.vm.usejit false + $(ADB) shell start .PHONY: use-art-jit use-art-jit: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter "verify-at-runtime" - adb shell setprop dalvik.vm.image-dex2oat-filter "verify-at-runtime" - adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so - adb shell setprop dalvik.vm.usejit true - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter "verify-at-runtime" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "verify-at-runtime" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so + $(ADB) shell setprop dalvik.vm.usejit true + $(ADB) shell start .PHONY: use-art-interpret-only use-art-interpret-only: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter "interpret-only" - adb shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" - adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so - adb shell setprop dalvik.vm.usejit false - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter "interpret-only" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so + $(ADB) shell setprop dalvik.vm.usejit false + $(ADB) shell start .PHONY: use-artd-interpret-only use-artd-interpret-only: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter "interpret-only" - adb shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" - adb shell setprop persist.sys.dalvik.vm.lib.2 libartd.so - adb shell setprop dalvik.vm.usejit false - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter "interpret-only" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "interpret-only" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so + $(ADB) shell setprop dalvik.vm.usejit false + $(ADB) shell start .PHONY: use-art-verify-none use-art-verify-none: - adb root - adb wait-for-device shell stop - adb shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* - adb shell setprop dalvik.vm.dex2oat-filter "verify-none" - adb shell setprop dalvik.vm.image-dex2oat-filter "verify-none" - adb shell setprop persist.sys.dalvik.vm.lib.2 libart.so - adb shell setprop dalvik.vm.usejit false - adb shell start + $(ADB) root + $(ADB) wait-for-device shell stop + $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/* + $(ADB) shell setprop dalvik.vm.dex2oat-filter "verify-none" + $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "verify-none" + $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so + $(ADB) shell setprop dalvik.vm.usejit false + $(ADB) shell start ######################################################################## diff --git a/build/Android.common.mk b/build/Android.common.mk index a6a9f0fc47..316ce646ab 100644 --- a/build/Android.common.mk +++ b/build/Android.common.mk @@ -92,4 +92,7 @@ else 2ND_ART_HOST_OUT_SHARED_LIBRARIES := $(2ND_HOST_OUT_SHARED_LIBRARIES) endif +ADB_EXECUTABLE := $(HOST_OUT_EXECUTABLES)/adb +ADB := $(ADB_EXECUTABLE) + endif # ART_ANDROID_COMMON_MK diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 2c073fec13..20f20c9f7b 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -461,14 +461,14 @@ $$(gtest_rule): PRIVATE_GTEST_WITNESS := $$(gtest_witness) .PHONY: $$(gtest_rule) $$(gtest_rule): test-art-target-sync - $(hide) adb shell touch $$(PRIVATE_GTEST_WITNESS) - $(hide) adb shell rm $$(PRIVATE_GTEST_WITNESS) - $(hide) adb shell $$(PRIVATE_MAYBE_CHROOT_COMMAND) chmod 755 $$(PRIVATE_TARGET_EXE) + $(hide) $(ADB) shell touch $$(PRIVATE_GTEST_WITNESS) + $(hide) $(ADB) shell rm $$(PRIVATE_GTEST_WITNESS) + $(hide) $(ADB) shell $$(PRIVATE_MAYBE_CHROOT_COMMAND) chmod 755 $$(PRIVATE_TARGET_EXE) $(hide) $$(call ART_TEST_SKIP,$$@) && \ - (adb shell "$$(PRIVATE_MAYBE_CHROOT_COMMAND) env $(GCOV_ENV) LD_LIBRARY_PATH=$(4) \ + ($(ADB) shell "$$(PRIVATE_MAYBE_CHROOT_COMMAND) env $(GCOV_ENV) LD_LIBRARY_PATH=$(4) \ ANDROID_ROOT=$(ART_GTEST_TARGET_ANDROID_ROOT) $$(PRIVATE_TARGET_EXE) \ && touch $$(PRIVATE_GTEST_WITNESS)" \ - && (adb pull $$(PRIVATE_GTEST_WITNESS) /tmp/ && $$(call ART_TEST_PASSED,$$@)) \ + && ($(ADB) pull $$(PRIVATE_GTEST_WITNESS) /tmp/ && $$(call ART_TEST_PASSED,$$@)) \ || $$(call ART_TEST_FAILED,$$@)) $(hide) rm -f /tmp/$$@-$$$$PPID diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index a33d53741c..42c6a5ff59 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -558,13 +558,12 @@ TEST_F(CmdlineParserTest, TestIgnoredArguments) { TEST_F(CmdlineParserTest, MultipleArguments) { EXPECT_TRUE(IsResultSuccessful(parser_->Parse( "-help -XX:ForegroundHeapGrowthMultiplier=0.5 " - "-Xnodex2oat -Xmethod-trace -XX:LargeObjectSpace=map"))); + "-Xmethod-trace -XX:LargeObjectSpace=map"))); auto&& map = parser_->ReleaseArgumentsMap(); - EXPECT_EQ(5u, map.Size()); + EXPECT_EQ(4u, map.Size()); EXPECT_KEY_VALUE(map, M::Help, Unit{}); EXPECT_KEY_VALUE(map, M::ForegroundHeapGrowthMultiplier, 0.5); - EXPECT_KEY_VALUE(map, M::Dex2Oat, false); EXPECT_KEY_VALUE(map, M::MethodTrace, Unit{}); EXPECT_KEY_VALUE(map, M::LargeObjectSpace, gc::space::LargeObjectSpaceType::kMap); } // TEST_F diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 21975dee68..f6afe2c958 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -1886,7 +1886,9 @@ void CompilerDriver::Verify(jobject jclass_loader, class VerifyClassVisitor : public CompilationVisitor { public: VerifyClassVisitor(const ParallelCompilationManager* manager, verifier::HardFailLogMode log_level) - : manager_(manager), log_level_(log_level) {} + : manager_(manager), + log_level_(log_level), + sdk_version_(Runtime::Current()->GetTargetSdkVersion()) {} void Visit(size_t class_def_index) REQUIRES(!Locks::mutator_lock_) override { ScopedTrace trace(__FUNCTION__); @@ -1923,6 +1925,7 @@ class VerifyClassVisitor : public CompilationVisitor { Runtime::Current()->GetCompilerCallbacks(), true /* allow soft failures */, log_level_, + sdk_version_, &error_msg); if (failure_kind == verifier::FailureKind::kHardFailure) { LOG(ERROR) << "Verification failed on class " << PrettyDescriptor(descriptor) @@ -1995,6 +1998,7 @@ class VerifyClassVisitor : public CompilationVisitor { private: const ParallelCompilationManager* const manager_; const verifier::HardFailLogMode log_level_; + const uint32_t sdk_version_; }; void CompilerDriver::VerifyDexFile(jobject class_loader, diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index a90ff3f885..e84896b113 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -1489,7 +1489,12 @@ void CodeGenerator::ValidateInvokeRuntime(QuickEntrypointEnum entrypoint, << " instruction->GetSideEffects().ToString()=" << instruction->GetSideEffects().ToString(); } else { - DCHECK(instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()) || + // 'CanTriggerGC' side effect is used to restrict optimization of instructions which depend + // on GC (e.g. IntermediateAddress) - to ensure they are not alive across GC points. However + // if execution never returns to the compiled code from a GC point this restriction is + // unnecessary - in particular for fatal slow paths which might trigger GC. + DCHECK((slow_path->IsFatal() && !instruction->GetLocations()->WillCall()) || + instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()) || // When (non-Baker) read barriers are enabled, some instructions // use a slow path to emit a read barrier, which does not trigger // GC. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 5feffa0511..68f1a2406a 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -1626,6 +1626,21 @@ using HConstInputsRef = TransformArrayRef<const HUserRecord<HInstruction*>, HInp * the same, and any reference read depends on any reference read without * further regard of its type). * + * kDependsOnGCBit is defined in the following way: instructions with kDependsOnGCBit must not be + * alive across the point where garbage collection might happen. + * + * Note: Instructions with kCanTriggerGCBit do not depend on each other. + * + * kCanTriggerGCBit must be used for instructions for which GC might happen on the path across + * those instructions from the compiler perspective (between this instruction and the next one + * in the IR). + * + * Note: Instructions which can cause GC only on a fatal slow path do not need + * kCanTriggerGCBit as the execution never returns to the instruction next to the exceptional + * one. However the execution may return to compiled code if there is a catch block in the + * current method; for this purpose the TryBoundary exit instruction has kCanTriggerGCBit + * set. + * * The internal representation uses 38-bit and is described in the table below. * The first line indicates the side effect, and for field/array accesses the * second line indicates the type of the access (in the order of the @@ -1698,10 +1713,17 @@ class SideEffects : public ValueObject { return SideEffects(TypeFlag(type, kArrayReadOffset)); } + // Returns whether GC might happen across this instruction from the compiler perspective so + // the next instruction in the IR would see that. + // + // See the SideEffect class comments. static SideEffects CanTriggerGC() { return SideEffects(1ULL << kCanTriggerGCBit); } + // Returns whether the instruction must not be alive across a GC point. + // + // See the SideEffect class comments. static SideEffects DependsOnGC() { return SideEffects(1ULL << kDependsOnGCBit); } @@ -3136,8 +3158,15 @@ class HTryBoundary final : public HExpression<0> { kLast = kExit }; + // SideEffects::CanTriggerGC prevents instructions with SideEffects::DependOnGC to be alive + // across the catch block entering edges as GC might happen during throwing an exception. + // TryBoundary with BoundaryKind::kExit is conservatively used for that as there is no + // HInstruction which a catch block must start from. explicit HTryBoundary(BoundaryKind kind, uint32_t dex_pc = kNoDexPc) - : HExpression(kTryBoundary, SideEffects::None(), dex_pc) { + : HExpression(kTryBoundary, + (kind == BoundaryKind::kExit) ? SideEffects::CanTriggerGC() + : SideEffects::None(), + dex_pc) { SetPackedField<BoundaryKindField>(kind); } @@ -5163,9 +5192,10 @@ class HAbs final : public HUnaryOperation { class HDivZeroCheck final : public HExpression<1> { public: // `HDivZeroCheck` can trigger GC, as it may call the `ArithmeticException` - // constructor. + // constructor. However it can only do it on a fatal slow path so execution never returns to the + // instruction following the current one; thus 'SideEffects::None()' is used. HDivZeroCheck(HInstruction* value, uint32_t dex_pc) - : HExpression(kDivZeroCheck, value->GetType(), SideEffects::CanTriggerGC(), dex_pc) { + : HExpression(kDivZeroCheck, value->GetType(), SideEffects::None(), dex_pc) { SetRawInputAt(0, value); } @@ -5642,9 +5672,10 @@ static constexpr uint32_t kNoRegNumber = -1; class HNullCheck final : public HExpression<1> { public: // `HNullCheck` can trigger GC, as it may call the `NullPointerException` - // constructor. + // constructor. However it can only do it on a fatal slow path so execution never returns to the + // instruction following the current one; thus 'SideEffects::None()' is used. HNullCheck(HInstruction* value, uint32_t dex_pc) - : HExpression(kNullCheck, value->GetType(), SideEffects::CanTriggerGC(), dex_pc) { + : HExpression(kNullCheck, value->GetType(), SideEffects::None(), dex_pc) { SetRawInputAt(0, value); } @@ -6071,12 +6102,13 @@ class HArrayLength final : public HExpression<1> { class HBoundsCheck final : public HExpression<2> { public: // `HBoundsCheck` can trigger GC, as it may call the `IndexOutOfBoundsException` - // constructor. + // constructor. However it can only do it on a fatal slow path so execution never returns to the + // instruction following the current one; thus 'SideEffects::None()' is used. HBoundsCheck(HInstruction* index, HInstruction* length, uint32_t dex_pc, bool is_string_char_at = false) - : HExpression(kBoundsCheck, index->GetType(), SideEffects::CanTriggerGC(), dex_pc) { + : HExpression(kBoundsCheck, index->GetType(), SideEffects::None(), dex_pc) { DCHECK_EQ(DataType::Type::kInt32, DataType::Kind(index->GetType())); SetPackedFlag<kFlagIsStringCharAt>(is_string_char_at); SetRawInputAt(0, index); diff --git a/compiler/optimizing/side_effects_test.cc b/compiler/optimizing/side_effects_test.cc index 97317124ef..4b0be07f3b 100644 --- a/compiler/optimizing/side_effects_test.cc +++ b/compiler/optimizing/side_effects_test.cc @@ -202,6 +202,7 @@ TEST(SideEffectsTest, GC) { EXPECT_TRUE(depends_on_gc.MayDependOn(all_changes)); EXPECT_TRUE(depends_on_gc.Union(can_trigger_gc).MayDependOn(all_changes)); EXPECT_FALSE(can_trigger_gc.MayDependOn(all_changes)); + EXPECT_FALSE(can_trigger_gc.MayDependOn(can_trigger_gc)); EXPECT_TRUE(all_changes.Includes(can_trigger_gc)); EXPECT_FALSE(all_changes.Includes(depends_on_gc)); diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 136066d074..81932a90ce 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -178,7 +178,8 @@ class VerifierDepsTest : public CommonCompilerTest { true /* allow_soft_failures */, true /* need_precise_constants */, false /* verify to dump */, - true /* allow_thread_suspension */); + true /* allow_thread_suspension */, + 0 /* api_level */); verifier.Verify(); soa.Self()->SetVerifierDeps(nullptr); has_failures = verifier.HasFailures(); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 5655b3c91d..9406c62a0e 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -1348,12 +1348,12 @@ class Dex2Oat final { } } } else { - std::unique_ptr<File> oat_file(new File(oat_fd_, oat_location_, /* check_usage */ true)); - if (oat_file == nullptr) { + std::unique_ptr<File> oat_file( + new File(DupCloexec(oat_fd_), oat_location_, /* check_usage */ true)); + if (!oat_file->IsOpened()) { PLOG(ERROR) << "Failed to create oat file: " << oat_location_; return false; } - oat_file->DisableAutoClose(); if (oat_file->SetLength(0) != 0) { PLOG(WARNING) << "Truncating oat file " << oat_location_ << " failed."; oat_file->Erase(); @@ -1385,12 +1385,12 @@ class Dex2Oat final { DCHECK_NE(output_vdex_fd_, -1); std::string vdex_location = ReplaceFileExtension(oat_location_, "vdex"); - std::unique_ptr<File> vdex_file(new File(output_vdex_fd_, vdex_location, /* check_usage */ true)); - if (vdex_file == nullptr) { + std::unique_ptr<File> vdex_file(new File( + DupCloexec(output_vdex_fd_), vdex_location, /* check_usage */ true)); + if (!vdex_file->IsOpened()) { PLOG(ERROR) << "Failed to create vdex file: " << vdex_location; return false; } - vdex_file->DisableAutoClose(); if (input_vdex_file_ != nullptr && output_vdex_fd_ == input_vdex_fd_) { update_input_vdex_ = true; } else { @@ -1472,10 +1472,7 @@ class Dex2Oat final { PLOG(ERROR) << "Failed to create swap file: " << swap_file_name_; return false; } - swap_fd_ = swap_file->Fd(); - swap_file->MarkUnchecked(); // We don't we to track this, it will be unlinked immediately. - swap_file->DisableAutoClose(); // We'll handle it ourselves, the File object will be - // released immediately. + swap_fd_ = swap_file->Release(); unlink(swap_file_name_.c_str()); } diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index b2fe441582..a1fed5f6d9 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -139,11 +139,11 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; @@ -159,11 +159,11 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() == nullptr); } @@ -516,11 +516,11 @@ class Dex2oatVeryLargeTest : public Dex2oatTest { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; EXPECT_GT(app_image_file.length(), 0u); @@ -787,11 +787,11 @@ class Dex2oatLayoutTest : public Dex2oatTest { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; @@ -949,11 +949,11 @@ class Dex2oatUnquickenTest : public Dex2oatTest { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; ASSERT_GE(odex_file->GetOatDexFiles().size(), 1u); @@ -1329,11 +1329,11 @@ TEST_F(Dex2oatTest, LayoutSections) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_filename.c_str(), oat_filename.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex->GetLocation().c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); std::vector<const OatDexFile*> oat_dex_files = odex_file->GetOatDexFiles(); @@ -1439,11 +1439,11 @@ TEST_F(Dex2oatTest, GenerateCompactDex) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_filename.c_str(), oat_filename.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); std::vector<const OatDexFile*> oat_dex_files = odex_file->GetOatDexFiles(); @@ -1684,11 +1684,11 @@ TEST_F(Dex2oatTest, CompactDexGenerationFailure) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_filename.c_str(), oat_filename.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, temp_dex.GetFilename().c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); std::vector<const OatDexFile*> oat_dex_files = odex_file->GetOatDexFiles(); @@ -1705,7 +1705,7 @@ TEST_F(Dex2oatTest, CompactDexGenerationFailureMultiDex) { // Create a multidex file with only one dex that gets rejected for cdex conversion. ScratchFile apk_file; { - FILE* file = fdopen(apk_file.GetFd(), "w+b"); + FILE* file = fdopen(dup(apk_file.GetFd()), "w+b"); ZipWriter writer(file); // Add vdex to zip. writer.StartEntry("classes.dex", ZipWriter::kCompress); @@ -1762,11 +1762,11 @@ TEST_F(Dex2oatTest, VerifyCompilationReason) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); ASSERT_STREQ("install", odex_file->GetCompilationReason()); @@ -1788,11 +1788,11 @@ TEST_F(Dex2oatTest, VerifyNoCompilationReason) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); ASSERT_EQ(nullptr, odex_file->GetCompilationReason()); @@ -1826,11 +1826,11 @@ TEST_F(Dex2oatTest, DontExtract) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/ false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr) << dex_location; std::vector<const OatDexFile*> oat_dex_files = odex_file->GetOatDexFiles(); @@ -1847,7 +1847,7 @@ TEST_F(Dex2oatTest, DontExtract) { std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex_location.c_str())); ASSERT_TRUE(vdex_file != nullptr); ASSERT_GT(vdex_file->GetLength(), 0u); - FILE* file = fdopen(dm_file.GetFd(), "w+b"); + FILE* file = fdopen(dup(dm_file.GetFd()), "w+b"); ZipWriter writer(file); auto write_all_bytes = [&](File* file) { std::unique_ptr<uint8_t[]> bytes(new uint8_t[file->GetLength()]); @@ -1973,7 +1973,7 @@ TEST_F(Dex2oatTest, QuickenedInput) { TEST_F(Dex2oatTest, CompactDexInvalidSource) { ScratchFile invalid_dex; { - FILE* file = fdopen(invalid_dex.GetFd(), "w+b"); + FILE* file = fdopen(dup(invalid_dex.GetFd()), "w+b"); ZipWriter writer(file); writer.StartEntry("classes.dex", ZipWriter::kAlign32); DexFile::Header header = {}; @@ -2015,7 +2015,7 @@ TEST_F(Dex2oatTest, CompactDexInZip) { // Create a zip containing the invalid dex. ScratchFile invalid_dex_zip; { - FILE* file = fdopen(invalid_dex_zip.GetFd(), "w+b"); + FILE* file = fdopen(dup(invalid_dex_zip.GetFd()), "w+b"); ZipWriter writer(file); writer.StartEntry("classes.dex", ZipWriter::kCompress); ASSERT_GE(writer.WriteBytes(&header, sizeof(header)), 0); @@ -2065,11 +2065,11 @@ TEST_F(Dex2oatTest, AppImageNoProfile) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, odex_location.c_str(), odex_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, odex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); ImageHeader header = {}; diff --git a/dex2oat/linker/elf_writer_test.cc b/dex2oat/linker/elf_writer_test.cc index b2be003b5d..40495f33ee 100644 --- a/dex2oat/linker/elf_writer_test.cc +++ b/dex2oat/linker/elf_writer_test.cc @@ -14,9 +14,12 @@ * limitations under the License. */ +#include <sys/mman.h> // For the PROT_NONE constant. + #include "elf_file.h" #include "base/file_utils.h" +#include "base/mem_map.h" #include "base/unix_file/fd_file.h" #include "base/utils.h" #include "common_compiler_test.h" @@ -65,8 +68,8 @@ TEST_F(ElfWriterTest, dlsym) { { std::string error_msg; std::unique_ptr<ElfFile> ef(ElfFile::Open(file.get(), - false, - false, + /* writable */ false, + /* program_header_only */ false, /*low_4gb*/false, &error_msg)); CHECK(ef.get() != nullptr) << error_msg; @@ -77,9 +80,9 @@ TEST_F(ElfWriterTest, dlsym) { { std::string error_msg; std::unique_ptr<ElfFile> ef(ElfFile::Open(file.get(), - false, - false, - /*low_4gb*/false, + /* writable */ false, + /* program_header_only */ false, + /* low_4gb */ false, &error_msg)); CHECK(ef.get() != nullptr) << error_msg; EXPECT_ELF_FILE_ADDRESS(ef, dl_oatdata, "oatdata", true); @@ -87,16 +90,28 @@ TEST_F(ElfWriterTest, dlsym) { EXPECT_ELF_FILE_ADDRESS(ef, dl_oatlastword, "oatlastword", true); } { - uint8_t* base = reinterpret_cast<uint8_t*>(ART_BASE_ADDRESS); std::string error_msg; std::unique_ptr<ElfFile> ef(ElfFile::Open(file.get(), - false, - true, - /*low_4gb*/false, - &error_msg, - base)); + /* writable */ false, + /* program_header_only */ true, + /* low_4gb */ false, + &error_msg)); CHECK(ef.get() != nullptr) << error_msg; - CHECK(ef->Load(file.get(), false, /*low_4gb*/false, &error_msg)) << error_msg; + size_t size; + bool success = ef->GetLoadedSize(&size, &error_msg); + CHECK(success) << error_msg; + MemMap reservation = MemMap::MapAnonymous("ElfWriterTest#dlsym reservation", + /* addr */ nullptr, + RoundUp(size, kPageSize), + PROT_NONE, + /* low_4gb */ true, + &error_msg); + CHECK(reservation.IsValid()) << error_msg; + uint8_t* base = reservation.Begin(); + success = + ef->Load(file.get(), /* executable */ false, /* low_4gb */ false, &reservation, &error_msg); + CHECK(success) << error_msg; + CHECK(!reservation.IsValid()); EXPECT_EQ(reinterpret_cast<uintptr_t>(dl_oatdata) + reinterpret_cast<uintptr_t>(base), reinterpret_cast<uintptr_t>(ef->FindDynamicSymbolAddress("oatdata"))); EXPECT_EQ(reinterpret_cast<uintptr_t>(dl_oatexec) + reinterpret_cast<uintptr_t>(base), @@ -116,9 +131,9 @@ TEST_F(ElfWriterTest, CheckBuildIdPresent) { { std::string error_msg; std::unique_ptr<ElfFile> ef(ElfFile::Open(file.get(), - false, - false, - /*low_4gb*/false, + /* writable */ false, + /* program_header_only */ false, + /* low_4gb */ false, &error_msg)); CHECK(ef.get() != nullptr) << error_msg; EXPECT_TRUE(ef->HasSection(".note.gnu.build-id")); diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index 0264b09eda..bd09f239ba 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -406,11 +406,11 @@ TEST_F(OatTest, WriteRead) { std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, tmp_oat.GetFilename(), tmp_oat.GetFilename(), - nullptr, - nullptr, - false, - /*low_4gb*/true, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ true, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(oat_file.get() != nullptr) << error_msg; const OatHeader& oat_header = oat_file->GetOatHeader(); @@ -529,11 +529,11 @@ TEST_F(OatTest, EmptyTextSection) { std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, tmp_oat.GetFilename(), tmp_oat.GetFilename(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(oat_file != nullptr); EXPECT_LT(static_cast<size_t>(oat_file->Size()), @@ -607,11 +607,11 @@ void OatTest::TestDexFileInput(bool verify, bool low_4gb, bool use_profile) { std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/* zip_fd */ -1, tmp_oat.GetFilename(), tmp_oat.GetFilename(), - nullptr, - nullptr, - false, + /* requested_base */ nullptr, + /* executable */ false, low_4gb, - nullptr, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; if (low_4gb) { @@ -738,11 +738,11 @@ void OatTest::TestZipFileInput(bool verify) { std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/* zip_fd */ -1, tmp_oat.GetFilename(), tmp_oat.GetFilename(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); @@ -788,11 +788,11 @@ void OatTest::TestZipFileInput(bool verify) { std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/* zip_fd */ -1, tmp_oat.GetFilename(), tmp_oat.GetFilename(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); diff --git a/dexlayout/dexdiag_test.cc b/dexlayout/dexdiag_test.cc index 53145c22fa..60dd7e42a4 100644 --- a/dexlayout/dexdiag_test.cc +++ b/dexlayout/dexdiag_test.cc @@ -71,11 +71,11 @@ class DexDiagTest : public CommonRuntimeTest { std::unique_ptr<OatFile> oat(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); EXPECT_TRUE(oat != nullptr) << error_msg; return oat; diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index bd7a301a56..52d355b570 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1794,14 +1794,20 @@ bool DexLayout::OutputDexFile(const DexFile* input_dex_file, // If options_.output_dex_directory_ is non null, we are outputting to a file. if (options_.output_dex_directory_ != nullptr) { std::string output_location(options_.output_dex_directory_); - size_t last_slash = dex_file_location.rfind('/'); + const size_t last_slash = dex_file_location.rfind('/'); std::string dex_file_directory = dex_file_location.substr(0, last_slash + 1); if (output_location == dex_file_directory) { output_location = dex_file_location + ".new"; - } else if (last_slash != std::string::npos) { - output_location += dex_file_location.substr(last_slash); } else { - output_location += "/" + dex_file_location + ".new"; + if (!output_location.empty() && output_location.back() != '/') { + output_location += "/"; + } + const size_t separator = dex_file_location.rfind('!'); + if (separator != std::string::npos) { + output_location += dex_file_location.substr(separator + 1); + } else { + output_location += "classes.dex"; + } } new_file.reset(OS::CreateEmptyFile(output_location.c_str())); if (new_file == nullptr) { diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc index a20930b28b..187c68790a 100644 --- a/dexlayout/dexlayout_test.cc +++ b/dexlayout/dexlayout_test.cc @@ -267,7 +267,7 @@ class DexLayoutTest : public CommonRuntimeTest { ScratchFile dexlayout_output; const std::string& dexlayout_filename = dexlayout_output.GetFilename(); - for (const std::string &dex_file : GetLibCoreDexFileNames()) { + for (const std::string& dex_file : GetLibCoreDexFileNames()) { std::vector<std::string> dexdump_exec_argv = { dexdump, "-d", "-f", "-h", "-l", "plain", "-o", dexdump_filename, dex_file }; std::vector<std::string> dexlayout_args = @@ -293,31 +293,33 @@ class DexLayoutTest : public CommonRuntimeTest { const std::string& tmp_name = tmp_file.GetFilename(); size_t tmp_last_slash = tmp_name.rfind('/'); std::string tmp_dir = tmp_name.substr(0, tmp_last_slash + 1); + std::string unzip_dir = tmp_dir + "unzip/"; - for (const std::string &dex_file : GetLibCoreDexFileNames()) { + for (const std::string& dex_file : GetLibCoreDexFileNames()) { std::vector<std::string> dexlayout_args = { "-w", tmp_dir, "-o", tmp_name, dex_file }; if (!DexLayoutExec(dexlayout_args, error_msg, /*pass_default_cdex_option*/ false)) { return false; } - size_t dex_file_last_slash = dex_file.rfind('/'); - std::string dex_file_name = dex_file.substr(dex_file_last_slash + 1); + std::string dex_file_name = "classes.dex"; std::vector<std::string> unzip_exec_argv = - { "/usr/bin/unzip", dex_file, "classes.dex", "-d", tmp_dir}; + { "/usr/bin/unzip", dex_file, "classes.dex", "-d", unzip_dir}; if (!::art::Exec(unzip_exec_argv, error_msg)) { return false; } std::vector<std::string> diff_exec_argv = - { "/usr/bin/diff", tmp_dir + "classes.dex" , tmp_dir + dex_file_name }; + { "/usr/bin/diff", tmp_dir + "classes.dex" , unzip_dir + dex_file_name }; if (!::art::Exec(diff_exec_argv, error_msg)) { return false; } - if (!UnlinkFile(tmp_dir + "classes.dex")) { + if (!UnlinkFile(unzip_dir + "classes.dex")) { return false; } if (!UnlinkFile(tmp_dir + dex_file_name)) { return false; } + // Remove the unzip temp directory so that unlinking android_data doesn't fail. + EXPECT_EQ(rmdir(unzip_dir.c_str()), 0); } return true; } diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc index a63f326d5a..1d106b2cdb 100644 --- a/libartbase/base/file_utils.cc +++ b/libartbase/base/file_utils.cc @@ -279,4 +279,12 @@ bool LocationIsOnSystemFramework(const char* full_path) { return android::base::StartsWith(full_path, framework_path); } +int DupCloexec(int fd) { +#if defined(__linux__) + return fcntl(fd, F_DUPFD_CLOEXEC, 0); +#else + return dup(fd); +#endif +} + } // namespace art diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index 063393bd3b..c249bccc3c 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -78,6 +78,9 @@ bool LocationIsOnSystem(const char* location); // Return whether the location is on system/framework (i.e. android_root/framework). bool LocationIsOnSystemFramework(const char* location); +// dup(2), except setting the O_CLOEXEC flag atomically, when possible. +int DupCloexec(int fd); + } // namespace art #endif // ART_LIBARTBASE_BASE_FILE_UTILS_H_ diff --git a/libartbase/base/fuchsia_compat.h b/libartbase/base/fuchsia_compat.h deleted file mode 100644 index 018bac0528..0000000000 --- a/libartbase/base/fuchsia_compat.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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. - */ - -#ifndef ART_LIBARTBASE_BASE_FUCHSIA_COMPAT_H_ -#define ART_LIBARTBASE_BASE_FUCHSIA_COMPAT_H_ - -// stubs for features lacking in Fuchsia - -struct rlimit { - int rlim_cur; -}; - -#define RLIMIT_FSIZE (1) -#define RLIM_INFINITY (-1) -static int getrlimit(int resource, struct rlimit *rlim) { - LOG(FATAL) << "getrlimit not available for Fuchsia"; -} - -static int ashmem_create_region(const char *name, size_t size) { - LOG(FATAL) << "ashmem_create_region not available for Fuchsia"; -} - -#endif // ART_LIBARTBASE_BASE_FUCHSIA_COMPAT_H_ diff --git a/libartbase/base/mem_map.cc b/libartbase/base/mem_map.cc index d9760bd574..1bf553d293 100644 --- a/libartbase/base/mem_map.cc +++ b/libartbase/base/mem_map.cc @@ -23,6 +23,10 @@ #include <sys/resource.h> #endif +#if defined(__linux__) +#include <sys/prctl.h> +#endif + #include <map> #include <memory> #include <sstream> @@ -30,12 +34,6 @@ #include "android-base/stringprintf.h" #include "android-base/unique_fd.h" -#if !defined(__Fuchsia__) -#include "cutils/ashmem.h" -#else -#include "fuchsia_compat.h" -#endif - #include "allocator.h" #include "bit_utils.h" #include "globals.h" @@ -61,6 +59,9 @@ using Maps = AllocationTrackingMultiMap<void*, MemMap*, kAllocatorTagMaps>; // All the non-empty MemMaps. Use a multimap as we do a reserve-and-divide (eg ElfMap::Load()). static Maps* gMaps GUARDED_BY(MemMap::GetMemMapsLock()) = nullptr; +// A map containing unique strings used for indentifying anonymous mappings +static std::map<std::string, int> debugStrMap GUARDED_BY(MemMap::GetMemMapsLock()); + // Retrieve iterator to a `gMaps` entry that is known to exist. Maps::iterator GetGMapsEntry(const MemMap& map) REQUIRES(MemMap::GetMemMapsLock()) { DCHECK(map.IsValid()); @@ -226,6 +227,33 @@ bool MemMap::CheckMapRequest(uint8_t* expected_ptr, void* actual_ptr, size_t byt return false; } +bool MemMap::CheckReservation(uint8_t* expected_ptr, + size_t byte_count, + const char* name, + const MemMap& reservation, + /*out*/std::string* error_msg) { + if (!reservation.IsValid()) { + *error_msg = StringPrintf("Invalid reservation for %s", name); + return false; + } + DCHECK_ALIGNED(reservation.Begin(), kPageSize); + if (reservation.Begin() != expected_ptr) { + *error_msg = StringPrintf("Bad image reservation start for %s: %p instead of %p", + name, + reservation.Begin(), + expected_ptr); + return false; + } + if (byte_count > reservation.Size()) { + *error_msg = StringPrintf("Insufficient reservation, required %zu, available %zu", + byte_count, + reservation.Size()); + return false; + } + return true; +} + + #if USE_ART_LOW_4G_ALLOCATOR void* MemMap::TryMemMapLow4GB(void* ptr, size_t page_aligned_byte_count, @@ -246,18 +274,45 @@ void* MemMap::TryMemMapLow4GB(void* ptr, } #endif +void MemMap::SetDebugName(void* map_ptr, const char* name, size_t size) { + // Debug naming is only used for Android target builds. For Linux targets, + // we'll still call prctl but it wont do anything till we upstream the prctl. + if (kIsTargetFuchsia || !kIsTargetBuild) { + return; + } + + // lock as std::map is not thread-safe + std::lock_guard<std::mutex> mu(*mem_maps_lock_); + + std::string debug_friendly_name("dalvik-"); + debug_friendly_name += name; + auto it = debugStrMap.find(debug_friendly_name); + + if (it == debugStrMap.end()) { + it = debugStrMap.insert(std::make_pair(std::move(debug_friendly_name), 1)).first; + } + + DCHECK(it != debugStrMap.end()); +#if defined(PR_SET_VMA) && defined(__linux__) + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, map_ptr, size, it->first.c_str()); +#else + // Prevent variable unused compiler errors. + UNUSED(map_ptr, size); +#endif +} + MemMap MemMap::MapAnonymous(const char* name, uint8_t* addr, size_t byte_count, int prot, bool low_4gb, bool reuse, - std::string* error_msg, - bool use_ashmem) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg, + bool use_debug_name) { #ifndef __LP64__ UNUSED(low_4gb); #endif - use_ashmem = use_ashmem && !kIsTargetLinux && !kIsTargetFuchsia; if (byte_count == 0) { *error_msg = "Empty MemMap requested."; return Invalid(); @@ -269,46 +324,20 @@ MemMap MemMap::MapAnonymous(const char* name, // reuse means it is okay that it overlaps an existing page mapping. // Only use this if you actually made the page reservation yourself. CHECK(addr != nullptr); + DCHECK(reservation == nullptr); DCHECK(ContainedWithinExistingMap(addr, byte_count, error_msg)) << *error_msg; flags |= MAP_FIXED; - } - - if (use_ashmem) { - if (!kIsTargetBuild) { - // When not on Android (either host or assuming a linux target) ashmem is faked using - // files in /tmp. Ensure that such files won't fail due to ulimit restrictions. If they - // will then use a regular mmap. - struct rlimit rlimit_fsize; - CHECK_EQ(getrlimit(RLIMIT_FSIZE, &rlimit_fsize), 0); - use_ashmem = (rlimit_fsize.rlim_cur == RLIM_INFINITY) || - (page_aligned_byte_count < rlimit_fsize.rlim_cur); + } else if (reservation != nullptr) { + CHECK(addr != nullptr); + if (!CheckReservation(addr, byte_count, name, *reservation, error_msg)) { + return MemMap::Invalid(); } + flags |= MAP_FIXED; } unique_fd fd; - - if (use_ashmem) { - // android_os_Debug.cpp read_mapinfo assumes all ashmem regions associated with the VM are - // prefixed "dalvik-". - std::string debug_friendly_name("dalvik-"); - debug_friendly_name += name; - fd.reset(ashmem_create_region(debug_friendly_name.c_str(), page_aligned_byte_count)); - - if (fd.get() == -1) { - // We failed to create the ashmem region. Print a warning, but continue - // anyway by creating a true anonymous mmap with an fd of -1. It is - // better to use an unlabelled anonymous map than to fail to create a - // map at all. - PLOG(WARNING) << "ashmem_create_region failed for '" << name << "'"; - } else { - // We succeeded in creating the ashmem region. Use the created ashmem - // region as backing for the mmap. - flags &= ~MAP_ANONYMOUS; - } - } - // We need to store and potentially set an error number for pretty printing of errors int saved_errno = 0; @@ -341,6 +370,16 @@ MemMap MemMap::MapAnonymous(const char* name, if (!CheckMapRequest(addr, actual, page_aligned_byte_count, error_msg)) { return Invalid(); } + + if (use_debug_name) { + SetDebugName(actual, name, page_aligned_byte_count); + } + + if (reservation != nullptr) { + // Re-mapping was successful, transfer the ownership of the memory to the new MemMap. + DCHECK_EQ(actual, reservation->Begin()); + reservation->ReleaseReservedMemory(byte_count); + } return MemMap(name, reinterpret_cast<uint8_t*>(actual), byte_count, @@ -446,22 +485,30 @@ MemMap MemMap::MapFileAtAddress(uint8_t* expected_ptr, int fd, off_t start, bool low_4gb, - bool reuse, const char* filename, - std::string* error_msg) { + bool reuse, + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { CHECK_NE(0, prot); CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE)); - // Note that we do not allow MAP_FIXED unless reuse == true, i.e we - // expect his mapping to be contained within an existing map. + // Note that we do not allow MAP_FIXED unless reuse == true or we have an existing + // reservation, i.e we expect this mapping to be contained within an existing map. if (reuse) { // reuse means it is okay that it overlaps an existing page mapping. // Only use this if you actually made the page reservation yourself. CHECK(expected_ptr != nullptr); + DCHECK(reservation == nullptr); DCHECK(error_msg != nullptr); DCHECK(ContainedWithinExistingMap(expected_ptr, byte_count, error_msg)) << ((error_msg != nullptr) ? *error_msg : std::string()); flags |= MAP_FIXED; + } else if (reservation != nullptr) { + DCHECK(error_msg != nullptr); + if (!CheckReservation(expected_ptr, byte_count, filename, *reservation, error_msg)) { + return Invalid(); + } + flags |= MAP_FIXED; } else { CHECK_EQ(0, flags & MAP_FIXED); // Don't bother checking for an overlapping region here. We'll @@ -523,6 +570,11 @@ MemMap MemMap::MapFileAtAddress(uint8_t* expected_ptr, page_aligned_byte_count -= redzone_size; } + if (reservation != nullptr) { + // Re-mapping was successful, transfer the ownership of the memory to the new MemMap. + DCHECK_EQ(actual, reservation->Begin()); + reservation->ReleaseReservedMemory(byte_count); + } return MemMap(filename, actual + page_offset, byte_count, @@ -639,8 +691,7 @@ MemMap MemMap::RemapAtEnd(uint8_t* new_end, const char* tail_name, int tail_prot, std::string* error_msg, - bool use_ashmem) { - use_ashmem = use_ashmem && !kIsTargetLinux && !kIsTargetFuchsia; + bool use_debug_name) { DCHECK_GE(new_end, Begin()); DCHECK_LE(new_end, End()); DCHECK_LE(begin_ + size_, reinterpret_cast<uint8_t*>(base_begin_) + base_size_); @@ -666,19 +717,6 @@ MemMap MemMap::RemapAtEnd(uint8_t* new_end, unique_fd fd; int flags = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS; - if (use_ashmem) { - // android_os_Debug.cpp read_mapinfo assumes all ashmem regions associated with the VM are - // prefixed "dalvik-". - std::string debug_friendly_name("dalvik-"); - debug_friendly_name += tail_name; - fd.reset(ashmem_create_region(debug_friendly_name.c_str(), tail_base_size)); - flags = MAP_PRIVATE | MAP_FIXED; - if (fd.get() == -1) { - *error_msg = StringPrintf("ashmem_create_region failed for '%s': %s", - tail_name, strerror(errno)); - return Invalid(); - } - } MEMORY_TOOL_MAKE_UNDEFINED(tail_base_begin, tail_base_size); // Note: Do not explicitly unmap the tail region, mmap() with MAP_FIXED automatically @@ -703,12 +741,56 @@ MemMap MemMap::RemapAtEnd(uint8_t* new_end, auto it = GetGMapsEntry(*this); gMaps->erase(it); } + + if (use_debug_name) { + SetDebugName(actual, tail_name, tail_base_size); + } + size_ = new_size; base_size_ = new_base_size; // Return the new mapping. return MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false); } +MemMap MemMap::TakeReservedMemory(size_t byte_count) { + uint8_t* begin = Begin(); + ReleaseReservedMemory(byte_count); // Performs necessary DCHECK()s on this reservation. + size_t base_size = RoundUp(byte_count, kPageSize); + return MemMap(name_, begin, byte_count, begin, base_size, prot_, /* reuse */ false); +} + +void MemMap::ReleaseReservedMemory(size_t byte_count) { + // Check the reservation mapping. + DCHECK(IsValid()); + DCHECK(!reuse_); + DCHECK(!already_unmapped_); + DCHECK_EQ(redzone_size_, 0u); + DCHECK_EQ(begin_, base_begin_); + DCHECK_EQ(size_, base_size_); + DCHECK_ALIGNED(begin_, kPageSize); + DCHECK_ALIGNED(size_, kPageSize); + + // Check and round up the `byte_count`. + DCHECK_NE(byte_count, 0u); + DCHECK_LE(byte_count, size_); + byte_count = RoundUp(byte_count, kPageSize); + + if (byte_count == size_) { + Invalidate(); + } else { + // Shrink the reservation MemMap and update its `gMaps` entry. + std::lock_guard<std::mutex> mu(*mem_maps_lock_); + auto it = GetGMapsEntry(*this); + // TODO: When C++17 becomes available, use std::map<>::extract(), modify, insert. + gMaps->erase(it); + begin_ += byte_count; + size_ -= byte_count; + base_begin_ = begin_; + base_size_ = size_; + gMaps->emplace(base_begin_, this); + } +} + void MemMap::MadviseDontNeedAndZero() { if (base_begin_ != nullptr || base_size_ != 0) { if (!kMadviseZeroes) { diff --git a/libartbase/base/mem_map.h b/libartbase/base/mem_map.h index cd7d502796..20eda324e1 100644 --- a/libartbase/base/mem_map.h +++ b/libartbase/base/mem_map.h @@ -114,9 +114,15 @@ class MemMap { // of the source mmap is returned to the caller. bool ReplaceWith(/*in-out*/MemMap* source, /*out*/std::string* error); + // Set a debug friendly name for a map. It will be prefixed with "dalvik-". + static void SetDebugName(void* map_ptr, const char* name, size_t size); + // Request an anonymous region of length 'byte_count' and a requested base address. // Use null as the requested base address if you don't care. - // "reuse" allows re-mapping an address range from an existing mapping. + // + // `reuse` allows re-mapping an address range from an existing mapping which retains the + // ownership of the memory. Alternatively, `reservation` allows re-mapping the start of an + // existing reservation mapping, transferring the ownership of the memory to the new MemMap. // // The word "anonymous" in this context means "not backed by a file". The supplied // 'name' will be used -- on systems that support it -- to give the mapping @@ -129,15 +135,23 @@ class MemMap { int prot, bool low_4gb, bool reuse, - std::string* error_msg, - bool use_ashmem = true); + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg, + bool use_debug_name = true); static MemMap MapAnonymous(const char* name, uint8_t* addr, size_t byte_count, int prot, bool low_4gb, - std::string* error_msg) { - return MapAnonymous(name, addr, byte_count, prot, low_4gb, /* reuse */ false, error_msg); + /*out*/std::string* error_msg) { + return MapAnonymous(name, + addr, + byte_count, + prot, + low_4gb, + /* reuse */ false, + /* reservation */ nullptr, + error_msg); } // Create placeholder for a region allocated by direct call to mmap. @@ -164,18 +178,23 @@ class MemMap { flags, fd, start, - /*low_4gb*/low_4gb, - /*reuse*/false, + /* low_4gb */ low_4gb, filename, + /* reuse */ false, + /* reservation */ nullptr, error_msg); } // Map part of a file, taking care of non-page aligned offsets. The "start" offset is absolute, // not relative. This version allows requesting a specific address for the base of the mapping. - // "reuse" allows us to create a view into an existing mapping where we do not take ownership of - // the memory. If error_msg is null then we do not print /proc/maps to the log if - // MapFileAtAddress fails. This helps improve performance of the fail case since reading and - // printing /proc/maps takes several milliseconds in the worst case. + // + // `reuse` allows re-mapping an address range from an existing mapping which retains the + // ownership of the memory. Alternatively, `reservation` allows re-mapping the start of an + // existing reservation mapping, transferring the ownership of the memory to the new MemMap. + // + // If error_msg is null then we do not print /proc/maps to the log if MapFileAtAddress fails. + // This helps improve performance of the fail case since reading and printing /proc/maps takes + // several milliseconds in the worst case. // // On success, returns returns a valid MemMap. On failure, returns an invalid MemMap. static MemMap MapFileAtAddress(uint8_t* addr, @@ -185,9 +204,10 @@ class MemMap { int fd, off_t start, bool low_4gb, - bool reuse, const char* filename, - std::string* error_msg); + bool reuse, + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg); const std::string& GetName() const { return name_; @@ -239,7 +259,15 @@ class MemMap { const char* tail_name, int tail_prot, std::string* error_msg, - bool use_ashmem = true); + bool use_debug_name = true); + + // Take ownership of pages at the beginning of the mapping. The mapping must be an + // anonymous reservation mapping, owning entire pages. The `byte_count` must not + // exceed the size of this reservation. + // + // Returns a mapping owning `byte_count` bytes rounded up to entire pages + // with size set to the passed `byte_count`. + MemMap TakeReservedMemory(size_t byte_count); static bool CheckNoGaps(MemMap& begin_map, MemMap& end_map) REQUIRES(!MemMap::mem_maps_lock_); @@ -304,12 +332,21 @@ class MemMap { off_t offset) REQUIRES(!MemMap::mem_maps_lock_); + // Release memory owned by a reservation mapping. + void ReleaseReservedMemory(size_t byte_count); + // member function to access real_munmap static bool CheckMapRequest(uint8_t* expected_ptr, void* actual_ptr, size_t byte_count, std::string* error_msg); + static bool CheckReservation(uint8_t* expected_ptr, + size_t byte_count, + const char* name, + const MemMap& reservation, + /*out*/std::string* error_msg); + std::string name_; uint8_t* begin_ = nullptr; // Start of data. May be changed by AlignBy. size_t size_ = 0u; // Length of data. diff --git a/libartbase/base/mem_map_test.cc b/libartbase/base/mem_map_test.cc index 396f12b421..ab3d18ff04 100644 --- a/libartbase/base/mem_map_test.cc +++ b/libartbase/base/mem_map_test.cc @@ -540,6 +540,7 @@ TEST_F(MemMapTest, MapAnonymousReuse) { PROT_READ | PROT_WRITE, /* low_4gb */ false, /* reuse */ false, + /* reservation */ nullptr, &error_msg); ASSERT_TRUE(map.IsValid()); ASSERT_TRUE(error_msg.empty()); @@ -549,6 +550,7 @@ TEST_F(MemMapTest, MapAnonymousReuse) { PROT_READ | PROT_WRITE, /* low_4gb */ false, /* reuse */ true, + /* reservation */ nullptr, &error_msg); ASSERT_TRUE(map2.IsValid()); ASSERT_TRUE(error_msg.empty()); @@ -720,4 +722,108 @@ TEST_F(MemMapTest, AlignBy) { } } +TEST_F(MemMapTest, Reservation) { + CommonInit(); + std::string error_msg; + ScratchFile scratch_file; + constexpr size_t kMapSize = 5 * kPageSize; + std::unique_ptr<uint8_t[]> data(new uint8_t[kMapSize]()); + ASSERT_TRUE(scratch_file.GetFile()->WriteFully(&data[0], kMapSize)); + + MemMap reservation = MemMap::MapAnonymous("Test reservation", + /* addr */ nullptr, + kMapSize, + PROT_NONE, + /* low_4gb */ false, + &error_msg); + ASSERT_TRUE(reservation.IsValid()); + ASSERT_TRUE(error_msg.empty()); + + // Map first part of the reservation. + constexpr size_t kChunk1Size = kPageSize - 1u; + static_assert(kChunk1Size < kMapSize, "We want to split the reservation."); + uint8_t* addr1 = reservation.Begin(); + MemMap map1 = MemMap::MapFileAtAddress(addr1, + /* byte_count */ kChunk1Size, + PROT_READ, + MAP_PRIVATE, + scratch_file.GetFd(), + /* start */ 0, + /* low_4gb */ false, + scratch_file.GetFilename().c_str(), + /* reuse */ false, + &reservation, + &error_msg); + ASSERT_TRUE(map1.IsValid()) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map1.Size(), kChunk1Size); + ASSERT_EQ(addr1, map1.Begin()); + ASSERT_TRUE(reservation.IsValid()); + // Entire pages are taken from the `reservation`. + ASSERT_LT(map1.End(), map1.BaseEnd()); + ASSERT_EQ(map1.BaseEnd(), reservation.Begin()); + + // Map second part as an anonymous mapping. + constexpr size_t kChunk2Size = 2 * kPageSize; + DCHECK_LT(kChunk2Size, reservation.Size()); // We want to split the reservation. + uint8_t* addr2 = reservation.Begin(); + MemMap map2 = MemMap::MapAnonymous("MiddleReservation", + addr2, + /* byte_count */ kChunk2Size, + PROT_READ, + /* low_4gb */ false, + /* reuse */ false, + &reservation, + &error_msg); + ASSERT_TRUE(map2.IsValid()) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map2.Size(), kChunk2Size); + ASSERT_EQ(addr2, map2.Begin()); + ASSERT_EQ(map2.End(), map2.BaseEnd()); // kChunk2Size is page aligned. + ASSERT_EQ(map2.BaseEnd(), reservation.Begin()); + + // Map the rest of the reservation except the last byte. + const size_t kChunk3Size = reservation.Size() - 1u; + uint8_t* addr3 = reservation.Begin(); + MemMap map3 = MemMap::MapFileAtAddress(addr3, + /* byte_count */ kChunk3Size, + PROT_READ, + MAP_PRIVATE, + scratch_file.GetFd(), + /* start */ dchecked_integral_cast<size_t>(addr3 - addr1), + /* low_4gb */ false, + scratch_file.GetFilename().c_str(), + /* reuse */ false, + &reservation, + &error_msg); + ASSERT_TRUE(map3.IsValid()) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map3.Size(), kChunk3Size); + ASSERT_EQ(addr3, map3.Begin()); + // Entire pages are taken from the `reservation`, so it's now exhausted. + ASSERT_FALSE(reservation.IsValid()); + + // Now split the MiddleReservation. + constexpr size_t kChunk2ASize = kPageSize - 1u; + DCHECK_LT(kChunk2ASize, map2.Size()); // We want to split the reservation. + MemMap map2a = map2.TakeReservedMemory(kChunk2ASize); + ASSERT_TRUE(map2a.IsValid()) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map2a.Size(), kChunk2ASize); + ASSERT_EQ(addr2, map2a.Begin()); + ASSERT_TRUE(map2.IsValid()); + ASSERT_LT(map2a.End(), map2a.BaseEnd()); + ASSERT_EQ(map2a.BaseEnd(), map2.Begin()); + + // And take the rest of the middle reservation. + const size_t kChunk2BSize = map2.Size() - 1u; + uint8_t* addr2b = map2.Begin(); + MemMap map2b = map2.TakeReservedMemory(kChunk2BSize); + ASSERT_TRUE(map2b.IsValid()) << error_msg; + ASSERT_TRUE(error_msg.empty()); + ASSERT_EQ(map2b.Size(), kChunk2ASize); + ASSERT_EQ(addr2b, map2b.Begin()); + ASSERT_FALSE(map2.IsValid()); +} + } // namespace art diff --git a/libartbase/base/unix_file/fd_file.cc b/libartbase/base/unix_file/fd_file.cc index c5313e969d..d715670194 100644 --- a/libartbase/base/unix_file/fd_file.cc +++ b/libartbase/base/unix_file/fd_file.cc @@ -21,6 +21,10 @@ #include <sys/types.h> #include <unistd.h> +#if defined(__BIONIC__) +#include <android/fdsan.h> +#endif + #include <limits> #include <android-base/logging.h> @@ -36,26 +40,34 @@ namespace unix_file { -FdFile::FdFile() - : guard_state_(GuardState::kClosed), fd_(-1), auto_close_(true), read_only_mode_(false) { +#if defined(__BIONIC__) +static uint64_t GetFdFileOwnerTag(FdFile* fd_file) { + return android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_ART_FDFILE, + reinterpret_cast<uint64_t>(fd_file)); } +#endif FdFile::FdFile(int fd, bool check_usage) - : guard_state_(check_usage ? GuardState::kBase : GuardState::kNoCheck), - fd_(fd), auto_close_(true), read_only_mode_(false) { -} + : FdFile(fd, std::string(), check_usage) {} FdFile::FdFile(int fd, const std::string& path, bool check_usage) - : FdFile(fd, path, check_usage, false) { -} + : FdFile(fd, path, check_usage, false) {} -FdFile::FdFile(int fd, const std::string& path, bool check_usage, bool read_only_mode) +FdFile::FdFile(int fd, const std::string& path, bool check_usage, + bool read_only_mode) : guard_state_(check_usage ? GuardState::kBase : GuardState::kNoCheck), - fd_(fd), file_path_(path), auto_close_(true), read_only_mode_(read_only_mode) { + fd_(fd), + file_path_(path), + read_only_mode_(read_only_mode) { +#if defined(__BIONIC__) + if (fd >= 0) { + android_fdsan_exchange_owner_tag(fd, 0, GetFdFileOwnerTag(this)); + } +#endif } -FdFile::FdFile(const std::string& path, int flags, mode_t mode, bool check_usage) - : fd_(-1), auto_close_(true) { +FdFile::FdFile(const std::string& path, int flags, mode_t mode, + bool check_usage) { Open(path, flags, mode); if (!check_usage || !IsOpened()) { guard_state_ = GuardState::kNoCheck; @@ -72,13 +84,27 @@ void FdFile::Destroy() { } DCHECK_GE(guard_state_, GuardState::kClosed); } - if (auto_close_ && fd_ != -1) { + if (fd_ != -1) { if (Close() != 0) { PLOG(WARNING) << "Failed to close file with fd=" << fd_ << " path=" << file_path_; } } } +FdFile::FdFile(FdFile&& other) + : guard_state_(other.guard_state_), + fd_(other.fd_), + file_path_(std::move(other.file_path_)), + read_only_mode_(other.read_only_mode_) { +#if defined(__BIONIC__) + if (fd_ >= 0) { + android_fdsan_exchange_owner_tag(fd_, GetFdFileOwnerTag(&other), GetFdFileOwnerTag(this)); + } +#endif + other.guard_state_ = GuardState::kClosed; + other.fd_ = -1; +} + FdFile& FdFile::operator=(FdFile&& other) { if (this == &other) { return *this; @@ -91,10 +117,15 @@ FdFile& FdFile::operator=(FdFile&& other) { guard_state_ = other.guard_state_; fd_ = other.fd_; file_path_ = std::move(other.file_path_); - auto_close_ = other.auto_close_; read_only_mode_ = other.read_only_mode_; - other.Release(); // Release other. +#if defined(__BIONIC__) + if (fd_ >= 0) { + android_fdsan_exchange_owner_tag(fd_, GetFdFileOwnerTag(&other), GetFdFileOwnerTag(this)); + } +#endif + other.guard_state_ = GuardState::kClosed; + other.fd_ = -1; return *this; } @@ -102,6 +133,39 @@ FdFile::~FdFile() { Destroy(); } +int FdFile::Release() { + int tmp_fd = fd_; + fd_ = -1; + guard_state_ = GuardState::kNoCheck; +#if defined(__BIONIC__) + if (tmp_fd >= 0) { + android_fdsan_exchange_owner_tag(tmp_fd, GetFdFileOwnerTag(this), 0); + } +#endif + return tmp_fd; +} + +void FdFile::Reset(int fd, bool check_usage) { + CHECK_NE(fd, fd_); + + if (fd_ != -1) { + Destroy(); + } + fd_ = fd; + +#if defined(__BIONIC__) + if (fd_ >= 0) { + android_fdsan_exchange_owner_tag(fd_, 0, GetFdFileOwnerTag(this)); + } +#endif + + if (check_usage) { + guard_state_ = fd == -1 ? GuardState::kNoCheck : GuardState::kBase; + } else { + guard_state_ = GuardState::kNoCheck; + } +} + void FdFile::moveTo(GuardState target, GuardState warn_threshold, const char* warning) { if (kCheckSafeUsage) { if (guard_state_ < GuardState::kNoCheck) { @@ -125,10 +189,6 @@ void FdFile::moveUp(GuardState target, const char* warning) { } } -void FdFile::DisableAutoClose() { - auto_close_ = false; -} - bool FdFile::Open(const std::string& path, int flags) { return Open(path, flags, 0640); } @@ -141,6 +201,11 @@ bool FdFile::Open(const std::string& path, int flags, mode_t mode) { if (fd_ == -1) { return false; } + +#if defined(__BIONIC__) + android_fdsan_exchange_owner_tag(fd_, 0, GetFdFileOwnerTag(this)); +#endif + file_path_ = path; if (kCheckSafeUsage && (flags & (O_RDWR | O_CREAT | O_WRONLY)) != 0) { // Start in the base state (not flushed, not closed). @@ -154,7 +219,11 @@ bool FdFile::Open(const std::string& path, int flags, mode_t mode) { } int FdFile::Close() { +#if defined(__BIONIC__) + int result = android_fdsan_close_with_tag(fd_, GetFdFileOwnerTag(this)); +#else int result = close(fd_); +#endif // Test here, so the file is closed and not leaked. if (kCheckSafeUsage) { diff --git a/libartbase/base/unix_file/fd_file.h b/libartbase/base/unix_file/fd_file.h index 19be3ef6f7..e362ed13ce 100644 --- a/libartbase/base/unix_file/fd_file.h +++ b/libartbase/base/unix_file/fd_file.h @@ -34,9 +34,9 @@ static constexpr bool kCheckSafeUsage = true; // Not thread safe. class FdFile : public RandomAccessFile { public: - FdFile(); - // Creates an FdFile using the given file descriptor. Takes ownership of the - // file descriptor. (Use DisableAutoClose to retain ownership.) + FdFile() = default; + // Creates an FdFile using the given file descriptor. + // Takes ownership of the file descriptor. FdFile(int fd, bool checkUsage); FdFile(int fd, const std::string& path, bool checkUsage); FdFile(int fd, const std::string& path, bool checkUsage, bool read_only_mode); @@ -46,40 +46,16 @@ class FdFile : public RandomAccessFile { FdFile(const std::string& path, int flags, mode_t mode, bool checkUsage); // Move constructor. - FdFile(FdFile&& other) - : guard_state_(other.guard_state_), - fd_(other.fd_), - file_path_(std::move(other.file_path_)), - auto_close_(other.auto_close_), - read_only_mode_(other.read_only_mode_) { - other.Release(); // Release the src. - } + FdFile(FdFile&& other); // Move assignment operator. FdFile& operator=(FdFile&& other); // Release the file descriptor. This will make further accesses to this FdFile invalid. Disables // all further state checking. - int Release() { - int tmp_fd = fd_; - fd_ = -1; - guard_state_ = GuardState::kNoCheck; - auto_close_ = false; - return tmp_fd; - } + int Release(); - void Reset(int fd, bool check_usage) { - if (fd_ != -1 && fd_ != fd) { - Destroy(); - } - fd_ = fd; - if (check_usage) { - guard_state_ = fd == -1 ? GuardState::kNoCheck : GuardState::kBase; - } else { - guard_state_ = GuardState::kNoCheck; - } - // Keep the auto_close_ state. - } + void Reset(int fd, bool check_usage); // Destroys an FdFile, closing the file descriptor if Close hasn't already // been called. (If you care about the return value of Close, call it @@ -121,7 +97,6 @@ class FdFile : public RandomAccessFile { const std::string& GetPath() const { return file_path_; } - void DisableAutoClose(); bool ReadFully(void* buffer, size_t byte_count) WARN_UNUSED; bool PreadFully(void* buffer, size_t byte_count, size_t offset) WARN_UNUSED; bool WriteFully(const void* buffer, size_t byte_count) WARN_UNUSED; @@ -168,7 +143,7 @@ class FdFile : public RandomAccessFile { } } - GuardState guard_state_; + GuardState guard_state_ = GuardState::kClosed; // Opens file 'file_path' using 'flags' and 'mode'. bool Open(const std::string& file_path, int flags); @@ -180,10 +155,9 @@ class FdFile : public RandomAccessFile { void Destroy(); // For ~FdFile and operator=(&&). - int fd_; + int fd_ = -1; std::string file_path_; - bool auto_close_; - bool read_only_mode_; + bool read_only_mode_ = false; DISALLOW_COPY_AND_ASSIGN(FdFile); }; diff --git a/libartbase/base/unix_file/fd_file_test.cc b/libartbase/base/unix_file/fd_file_test.cc index 1f731a7a7b..298b2d785f 100644 --- a/libartbase/base/unix_file/fd_file_test.cc +++ b/libartbase/base/unix_file/fd_file_test.cc @@ -24,7 +24,10 @@ namespace unix_file { class FdFileTest : public RandomAccessFileTest { protected: virtual RandomAccessFile* MakeTestFile() { - return new FdFile(fileno(tmpfile()), false); + FILE* tmp = tmpfile(); + int fd = dup(fileno(tmp)); + fclose(tmp); + return new FdFile(fd, false); } }; diff --git a/libartbase/base/zip_archive.cc b/libartbase/base/zip_archive.cc index a841bae8ea..174d22792a 100644 --- a/libartbase/base/zip_archive.cc +++ b/libartbase/base/zip_archive.cc @@ -133,16 +133,14 @@ MemMap ZipEntry::MapDirectlyFromFile(const char* zip_filename, std::string* erro } MemMap map = - MemMap::MapFileAtAddress(nullptr, // Expected pointer address - GetUncompressedLength(), // Byte count - PROT_READ | PROT_WRITE, - MAP_PRIVATE, - zip_fd, - offset, - false, // Don't restrict allocation to lower4GB - false, // Doesn't overlap existing map (reuse=false) - name.c_str(), - /*out*/error_msg); + MemMap::MapFile(GetUncompressedLength(), // Byte count + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + zip_fd, + offset, + /* low_4gb */ false, + name.c_str(), + error_msg); if (!map.IsValid()) { DCHECK(!error_msg->empty()); diff --git a/libartbase/base/zip_archive_test.cc b/libartbase/base/zip_archive_test.cc index b99a471e04..b9238811ca 100644 --- a/libartbase/base/zip_archive_test.cc +++ b/libartbase/base/zip_archive_test.cc @@ -41,7 +41,7 @@ TEST_F(ZipArchiveTest, FindAndExtract) { ScratchFile tmp; ASSERT_NE(-1, tmp.GetFd()); - std::unique_ptr<File> file(new File(tmp.GetFd(), tmp.GetFilename(), false)); + std::unique_ptr<File> file(new File(dup(tmp.GetFd()), tmp.GetFilename(), false)); ASSERT_TRUE(file.get() != nullptr); bool success = zip_entry->ExtractToFile(*file, &error_msg); ASSERT_TRUE(success) << error_msg; diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc index eb7d3d3308..4f73967353 100644 --- a/libdexfile/dex/art_dex_file_loader.cc +++ b/libdexfile/dex/art_dex_file_loader.cc @@ -95,7 +95,7 @@ bool ArtDexFileLoader::GetMultiDexChecksums(const char* filename, File fd; if (zip_fd != -1) { if (ReadMagicAndReset(zip_fd, &magic, error_msg)) { - fd = File(zip_fd, false /* check_usage */); + fd = File(DupCloexec(zip_fd), false /* check_usage */); } } else { fd = OpenAndReadMagic(filename, &magic, error_msg); diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc index c7653457aa..9b70e62f0c 100644 --- a/libprofile/profile/profile_compilation_info.cc +++ b/libprofile/profile/profile_compilation_info.cc @@ -34,6 +34,7 @@ #include "base/arena_allocator.h" #include "base/dumpable.h" +#include "base/file_utils.h" #include "base/logging.h" // For VLOG. #include "base/malloc_arena_pool.h" #include "base/os.h" @@ -1171,7 +1172,8 @@ ProfileCompilationInfo::ProfileLoadStatus ProfileCompilationInfo::OpenSource( source->reset(ProfileSource::Create(fd)); return kProfileLoadSuccess; } else { - std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, "profile", error)); + std::unique_ptr<ZipArchive> zip_archive( + ZipArchive::OpenFromFd(DupCloexec(fd), "profile", error)); if (zip_archive.get() == nullptr) { *error = "Could not open the profile zip archive"; return kProfileLoadBadData; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index fdf43d7632..d9be750259 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -1474,7 +1474,7 @@ class OatDumper { } return verifier::MethodVerifier::VerifyMethodAndDump( soa.Self(), vios, dex_method_idx, dex_file, dex_cache, *options_.class_loader_, - class_def, code_item, method, method_access_flags); + class_def, code_item, method, method_access_flags, /* api_level */ 0); } return nullptr; @@ -1825,11 +1825,11 @@ class ImageDumper { oat_file = OatFile::Open(/* zip_fd */ -1, oat_location, oat_location, - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg); } if (oat_file == nullptr) { @@ -2723,11 +2723,11 @@ static int DumpImages(Runtime* runtime, OatDumperOptions* options, std::ostream* std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, options->app_oat_, options->app_oat_, - nullptr, - nullptr, - false, - /*low_4gb*/true, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ true, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file " << options->app_oat_ << " with error " << error_msg; @@ -2847,11 +2847,11 @@ static int DumpOat(Runtime* runtime, std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, oat_filename, oat_filename, - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_filename, + /* reservation */ nullptr, &error_msg)); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; @@ -2873,11 +2873,11 @@ static int SymbolizeOat(const char* oat_filename, std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, oat_filename, oat_filename, - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_filename, + /* reservation */ nullptr, &error_msg)); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; @@ -2921,11 +2921,11 @@ class IMTDumper { std::unique_ptr<OatFile> oat_file(OatFile::Open(/* zip_fd */ -1, oat_filename, oat_filename, - nullptr, - nullptr, - false, + /* requested_base */ nullptr, + /* executable */ false, /*low_4gb*/false, dex_filename, + /* reservation */ nullptr, &error_msg)); if (oat_file == nullptr) { LOG(ERROR) << "Failed to open oat file from '" << oat_filename << "': " << error_msg; diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index 59f61e2ee4..3213bbe91a 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -506,13 +506,15 @@ class JvmtiFunctions { static jvmtiError IterateOverInstancesOfClass( jvmtiEnv* env, - jclass klass ATTRIBUTE_UNUSED, - jvmtiHeapObjectFilter object_filter ATTRIBUTE_UNUSED, - jvmtiHeapObjectCallback heap_object_callback ATTRIBUTE_UNUSED, - const void* user_data ATTRIBUTE_UNUSED) { + jclass klass, + jvmtiHeapObjectFilter object_filter, + jvmtiHeapObjectCallback heap_object_callback, + const void* user_data) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_tag_objects); - return ERR(NOT_IMPLEMENTED); + HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get()); + return heap_util.IterateOverInstancesOfClass( + env, klass, object_filter, heap_object_callback, user_data); } static jvmtiError GetLocalObject(jvmtiEnv* env, diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc index 85aa946356..6c79a602c3 100644 --- a/openjdkjvmti/ti_heap.cc +++ b/openjdkjvmti/ti_heap.cc @@ -653,6 +653,70 @@ void HeapUtil::Unregister() { art::Runtime::Current()->RemoveSystemWeakHolder(&gIndexCachingTable); } +jvmtiError HeapUtil::IterateOverInstancesOfClass(jvmtiEnv* env, + jclass klass, + jvmtiHeapObjectFilter filter, + jvmtiHeapObjectCallback cb, + const void* user_data) { + if (cb == nullptr || klass == nullptr) { + return ERR(NULL_POINTER); + } + + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); // Now we know we have the shared lock. + art::StackHandleScope<1> hs(self); + + art::ObjPtr<art::mirror::Object> klass_ptr(soa.Decode<art::mirror::Class>(klass)); + if (!klass_ptr->IsClass()) { + 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_) { + // Early return, as we can't really stop visiting. + if (stop_reports) { + return; + } + + art::ScopedAssertNoThreadSuspension no_suspension("IterateOverInstancesOfClass"); + + art::ObjPtr<art::mirror::Class> klass = obj->GetClass(); + + if (filter_klass != nullptr && !filter_klass->IsAssignableFrom(klass)) { + return; + } + + jlong tag = 0; + tag_table->GetTag(obj, &tag); + if ((filter != JVMTI_HEAP_OBJECT_EITHER) && + ((tag == 0 && filter == JVMTI_HEAP_OBJECT_TAGGED) || + (tag != 0 && filter == JVMTI_HEAP_OBJECT_UNTAGGED))) { + return; + } + + jlong class_tag = 0; + tag_table->GetTag(klass.Ptr(), &class_tag); + + jlong saved_tag = tag; + jint ret = cb(class_tag, obj->SizeOf(), &tag, const_cast<void*>(user_data)); + + stop_reports = (ret == JVMTI_ITERATION_ABORT); + + if (tag != saved_tag) { + tag_table->Set(obj, tag); + } + }; + art::Runtime::Current()->GetHeap()->VisitObjects(visitor); + + return OK; +} + template <typename T> static jvmtiError DoIterateThroughHeap(T fn, jvmtiEnv* env, diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h index 62761b500c..382d80f576 100644 --- a/openjdkjvmti/ti_heap.h +++ b/openjdkjvmti/ti_heap.h @@ -30,6 +30,12 @@ class HeapUtil { jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr); + jvmtiError IterateOverInstancesOfClass(jvmtiEnv* env, + jclass klass, + jvmtiHeapObjectFilter filter, + jvmtiHeapObjectCallback cb, + const void* user_data); + jvmtiError IterateThroughHeap(jvmtiEnv* env, jint heap_filter, jclass klass, diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index 2ec2f04e73..db2b143022 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -1122,6 +1122,7 @@ bool Redefiner::ClassRedefinition::CheckVerification(const RedefinitionDataIter& true, /*allow_soft_failures*/ /*log_level*/ art::verifier::HardFailLogMode::kLogWarning, + art::Runtime::Current()->GetTargetSdkVersion(), &error); switch (failure) { case art::verifier::FailureKind::kNoFailure: diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 65f05d9362..ccb42c4ba2 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -4169,6 +4169,7 @@ verifier::FailureKind ClassLinker::PerformClassVerification(Thread* self, runtime->GetCompilerCallbacks(), runtime->IsAotCompiler(), log_level, + Runtime::Current()->GetTargetSdkVersion(), error_msg); } diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index 9e3159d5e7..93af77fe23 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -20,6 +20,8 @@ #include <backtrace/BacktraceMap.h> #include <gtest/gtest.h> +#include "android-base/stringprintf.h" +#include "android-base/strings.h" #include "base/file_utils.h" #include "base/mem_map.h" #include "common_runtime_test.h" @@ -27,6 +29,7 @@ #include "dex2oat_environment_test.h" #include "dexopt_test.h" #include "gc/space/image_space.h" +#include "hidden_api.h" namespace art { void DexoptTest::SetUp() { @@ -45,6 +48,46 @@ void DexoptTest::PostRuntimeCreate() { ReserveImageSpace(); } +static std::string ImageLocation() { + Runtime* runtime = Runtime::Current(); + const std::vector<gc::space::ImageSpace*>& image_spaces = + runtime->GetHeap()->GetBootImageSpaces(); + if (image_spaces.empty()) { + return ""; + } + return image_spaces[0]->GetImageLocation(); +} + +bool DexoptTest::Dex2Oat(const std::vector<std::string>& args, std::string* error_msg) { + Runtime* runtime = Runtime::Current(); + + std::vector<std::string> argv; + argv.push_back(runtime->GetCompilerExecutable()); + if (runtime->IsJavaDebuggable()) { + argv.push_back("--debuggable"); + } + runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); + + if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kNoChecks) { + argv.push_back("--runtime-arg"); + argv.push_back("-Xhidden-api-checks"); + } + + if (!kIsTargetBuild) { + argv.push_back("--host"); + } + + argv.push_back("--boot-image=" + ImageLocation()); + + std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); + argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); + + argv.insert(argv.end(), args.begin(), args.end()); + + std::string command_line(android::base::Join(argv, ' ')); + return Exec(argv, error_msg); +} + void DexoptTest::GenerateOatForTest(const std::string& dex_location, const std::string& oat_location_in, CompilerFilter::Filter filter, @@ -96,7 +139,7 @@ void DexoptTest::GenerateOatForTest(const std::string& dex_location, } std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; if (!relocate) { // Restore the dalvik cache if needed. @@ -108,11 +151,11 @@ void DexoptTest::GenerateOatForTest(const std::string& dex_location, std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; EXPECT_EQ(pic, odex_file->IsPic()); diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h index df7181a1e7..5dff379a32 100644 --- a/runtime/dexopt_test.h +++ b/runtime/dexopt_test.h @@ -71,6 +71,8 @@ class DexoptTest : public Dex2oatEnvironmentTest { // Generate a standard oat file in the oat location. void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + static bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); + private: // Pre-Relocate the image to a known non-zero offset so we don't have to // deal with the runtime randomly relocating the image by 0 and messing up diff --git a/runtime/elf_file.cc b/runtime/elf_file.cc index d45a689fd6..e7715c4934 100644 --- a/runtime/elf_file.cc +++ b/runtime/elf_file.cc @@ -37,9 +37,7 @@ namespace art { using android::base::StringPrintf; template <typename ElfTypes> -ElfFileImpl<ElfTypes>::ElfFileImpl(File* file, bool writable, - bool program_header_only, - uint8_t* requested_base) +ElfFileImpl<ElfTypes>::ElfFileImpl(File* file, bool writable, bool program_header_only) : writable_(writable), program_header_only_(program_header_only), header_(nullptr), @@ -54,8 +52,7 @@ ElfFileImpl<ElfTypes>::ElfFileImpl(File* file, bool writable, dynstr_section_start_(nullptr), hash_section_start_(nullptr), symtab_symbol_table_(nullptr), - dynsym_symbol_table_(nullptr), - requested_base_(requested_base) { + dynsym_symbol_table_(nullptr) { CHECK(file != nullptr); } @@ -64,10 +61,9 @@ ElfFileImpl<ElfTypes>* ElfFileImpl<ElfTypes>::Open(File* file, bool writable, bool program_header_only, bool low_4gb, - std::string* error_msg, - uint8_t* requested_base) { - std::unique_ptr<ElfFileImpl<ElfTypes>> elf_file(new ElfFileImpl<ElfTypes> - (file, writable, program_header_only, requested_base)); + std::string* error_msg) { + std::unique_ptr<ElfFileImpl<ElfTypes>> elf_file( + new ElfFileImpl<ElfTypes>(file, writable, program_header_only)); int prot; int flags; if (writable) { @@ -89,9 +85,8 @@ ElfFileImpl<ElfTypes>* ElfFileImpl<ElfTypes>::Open(File* file, int flags, bool low_4gb, std::string* error_msg) { - std::unique_ptr<ElfFileImpl<ElfTypes>> elf_file(new ElfFileImpl<ElfTypes> - (file, (prot & PROT_WRITE) == PROT_WRITE, /*program_header_only*/false, - /*requested_base*/nullptr)); + std::unique_ptr<ElfFileImpl<ElfTypes>> elf_file( + new ElfFileImpl<ElfTypes>(file, (prot & PROT_WRITE) != 0, /* program_header_only */ false)); if (!elf_file->Setup(file, prot, flags, low_4gb, error_msg)) { return nullptr; } @@ -684,9 +679,7 @@ template <typename ElfTypes> typename ElfTypes::Phdr* ElfFileImpl<ElfTypes>::GetProgramHeader(Elf_Word i) const { CHECK_LT(i, GetProgramHeaderNum()) << file_path_; // Sanity check for caller. uint8_t* program_header = GetProgramHeadersStart() + (i * GetHeader().e_phentsize); - if (program_header >= End()) { - return nullptr; // Failure condition. - } + CHECK_LT(program_header, End()); return reinterpret_cast<Elf_Phdr*>(program_header); } @@ -1027,9 +1020,17 @@ typename ElfTypes::Rela& ElfFileImpl<ElfTypes>::GetRela(Elf_Shdr& section_header return *(GetRelaSectionStart(section_header) + i); } -// Base on bionic phdr_table_get_load_size template <typename ElfTypes> bool ElfFileImpl<ElfTypes>::GetLoadedSize(size_t* size, std::string* error_msg) const { + uint8_t* vaddr_begin; + return GetLoadedAddressRange(&vaddr_begin, size, error_msg); +} + +// Base on bionic phdr_table_get_load_size +template <typename ElfTypes> +bool ElfFileImpl<ElfTypes>::GetLoadedAddressRange(/*out*/uint8_t** vaddr_begin, + /*out*/size_t* vaddr_size, + /*out*/std::string* error_msg) const { Elf_Addr min_vaddr = static_cast<Elf_Addr>(-1); Elf_Addr max_vaddr = 0u; for (Elf_Word i = 0; i < GetProgramHeaderNum(); i++) { @@ -1048,7 +1049,8 @@ bool ElfFileImpl<ElfTypes>::GetLoadedSize(size_t* size, std::string* error_msg) << program_header->p_vaddr << "+0x" << program_header->p_memsz << "=0x" << end_vaddr << " in ELF file \"" << file_path_ << "\""; *error_msg = oss.str(); - *size = static_cast<size_t>(-1); + *vaddr_begin = nullptr; + *vaddr_size = static_cast<size_t>(-1); return false; } if (end_vaddr > max_vaddr) { @@ -1058,17 +1060,19 @@ bool ElfFileImpl<ElfTypes>::GetLoadedSize(size_t* size, std::string* error_msg) min_vaddr = RoundDown(min_vaddr, kPageSize); max_vaddr = RoundUp(max_vaddr, kPageSize); CHECK_LT(min_vaddr, max_vaddr) << file_path_; - Elf_Addr loaded_size = max_vaddr - min_vaddr; - // Check that the loaded_size fits in size_t. - if (UNLIKELY(loaded_size > std::numeric_limits<size_t>::max())) { + // Check that the range fits into the runtime address space. + if (UNLIKELY(max_vaddr - 1u > std::numeric_limits<size_t>::max())) { std::ostringstream oss; - oss << "Loaded size is 0x" << std::hex << loaded_size << " but maximum size_t is 0x" - << std::numeric_limits<size_t>::max() << " for ELF file \"" << file_path_ << "\""; + oss << "Loaded range is 0x" << std::hex << min_vaddr << "-0x" << max_vaddr + << " but maximum size_t is 0x" << std::numeric_limits<size_t>::max() + << " for ELF file \"" << file_path_ << "\""; *error_msg = oss.str(); - *size = static_cast<size_t>(-1); + *vaddr_begin = nullptr; + *vaddr_size = static_cast<size_t>(-1); return false; } - *size = loaded_size; + *vaddr_begin = reinterpret_cast<uint8_t*>(min_vaddr); + *vaddr_size = dchecked_integral_cast<size_t>(max_vaddr - min_vaddr); return true; } @@ -1099,7 +1103,8 @@ template <typename ElfTypes> bool ElfFileImpl<ElfTypes>::Load(File* file, bool executable, bool low_4gb, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { CHECK(program_header_only_) << file->GetPath(); if (executable) { @@ -1115,11 +1120,6 @@ bool ElfFileImpl<ElfTypes>::Load(File* file, bool reserved = false; for (Elf_Word i = 0; i < GetProgramHeaderNum(); i++) { Elf_Phdr* program_header = GetProgramHeader(i); - if (program_header == nullptr) { - *error_msg = StringPrintf("No program header for entry %d in ELF file %s.", - i, file->GetPath().c_str()); - return false; - } // Record .dynamic header information for later use if (program_header->p_type == PT_DYNAMIC) { @@ -1150,41 +1150,39 @@ bool ElfFileImpl<ElfTypes>::Load(File* file, } size_t file_length = static_cast<size_t>(temp_file_length); if (!reserved) { - uint8_t* reserve_base = reinterpret_cast<uint8_t*>(program_header->p_vaddr); - uint8_t* reserve_base_override = reserve_base; - // Override the base (e.g. when compiling with --compile-pic) - if (requested_base_ != nullptr) { - reserve_base_override = requested_base_; - } - std::string reservation_name("ElfFile reservation for "); - reservation_name += file->GetPath(); - size_t loaded_size; - if (!GetLoadedSize(&loaded_size, error_msg)) { + uint8_t* vaddr_begin; + size_t vaddr_size; + if (!GetLoadedAddressRange(&vaddr_begin, &vaddr_size, error_msg)) { DCHECK(!error_msg->empty()); return false; } - MemMap reserve = MemMap::MapAnonymous(reservation_name.c_str(), - reserve_base_override, - loaded_size, - PROT_NONE, - low_4gb, - error_msg); - if (!reserve.IsValid()) { + std::string reservation_name = "ElfFile reservation for " + file->GetPath(); + MemMap local_reservation = MemMap::MapAnonymous( + reservation_name.c_str(), + (reservation != nullptr) ? reservation->Begin() : nullptr, + vaddr_size, + PROT_NONE, + low_4gb, + /* reuse */ false, + reservation, + error_msg); + if (!local_reservation.IsValid()) { *error_msg = StringPrintf("Failed to allocate %s: %s", - reservation_name.c_str(), error_msg->c_str()); + reservation_name.c_str(), + error_msg->c_str()); return false; } reserved = true; - // Base address is the difference of actual mapped location and the p_vaddr - base_address_ = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(reserve.Begin()) - - reinterpret_cast<uintptr_t>(reserve_base)); + // Base address is the difference of actual mapped location and the vaddr_begin. + base_address_ = reinterpret_cast<uint8_t*>( + static_cast<uintptr_t>(local_reservation.Begin() - vaddr_begin)); // By adding the p_vaddr of a section/symbol to base_address_ we will always get the // dynamic memory address of where that object is actually mapped // // TODO: base_address_ needs to be calculated in ::Open, otherwise // FindDynamicSymbolAddress returns the wrong values until Load is called. - segments_.push_back(std::move(reserve)); + segments_.push_back(std::move(local_reservation)); } // empty segment, nothing to map if (program_header->p_memsz == 0) { @@ -1239,9 +1237,10 @@ bool ElfFileImpl<ElfTypes>::Load(File* file, flags, file->Fd(), program_header->p_offset, - /*low4_gb*/false, - /*reuse*/true, // implies MAP_FIXED + /* low4_gb */ false, file->GetPath().c_str(), + /* reuse */ true, // implies MAP_FIXED + /* reservation */ nullptr, error_msg); if (!segment.IsValid()) { *error_msg = StringPrintf("Failed to map ELF file segment %d from %s: %s", @@ -1265,6 +1264,7 @@ bool ElfFileImpl<ElfTypes>::Load(File* file, prot, /* low_4gb */ false, /* reuse */ true, + /* reservation */ nullptr, error_msg); if (!segment.IsValid()) { *error_msg = StringPrintf("Failed to map zero-initialized ELF file segment %d from %s: %s", @@ -1704,8 +1704,7 @@ ElfFile* ElfFile::Open(File* file, bool writable, bool program_header_only, bool low_4gb, - std::string* error_msg, - uint8_t* requested_base) { + /*out*/std::string* error_msg) { if (file->GetLength() < EI_NIDENT) { *error_msg = StringPrintf("File %s is too short to be a valid ELF file", file->GetPath().c_str()); @@ -1728,8 +1727,7 @@ ElfFile* ElfFile::Open(File* file, writable, program_header_only, low_4gb, - error_msg, - requested_base); + error_msg); if (elf_file_impl == nullptr) { return nullptr; } @@ -1739,8 +1737,7 @@ ElfFile* ElfFile::Open(File* file, writable, program_header_only, low_4gb, - error_msg, - requested_base); + error_msg); if (elf_file_impl == nullptr) { return nullptr; } @@ -1754,7 +1751,7 @@ ElfFile* ElfFile::Open(File* file, } } -ElfFile* ElfFile::Open(File* file, int mmap_prot, int mmap_flags, std::string* error_msg) { +ElfFile* ElfFile::Open(File* file, int mmap_prot, int mmap_flags, /*out*/std::string* error_msg) { // low_4gb support not required for this path. constexpr bool low_4gb = false; if (file->GetLength() < EI_NIDENT) { @@ -1811,8 +1808,12 @@ ElfFile* ElfFile::Open(File* file, int mmap_prot, int mmap_flags, std::string* e return elf32_->func(__VA_ARGS__); \ } -bool ElfFile::Load(File* file, bool executable, bool low_4gb, std::string* error_msg) { - DELEGATE_TO_IMPL(Load, file, executable, low_4gb, error_msg); +bool ElfFile::Load(File* file, + bool executable, + bool low_4gb, + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { + DELEGATE_TO_IMPL(Load, file, executable, low_4gb, reservation, error_msg); } const uint8_t* ElfFile::FindDynamicSymbolAddress(const std::string& symbol_name) const { diff --git a/runtime/elf_file.h b/runtime/elf_file.h index ab9e6fa2ec..8da7e1a29c 100644 --- a/runtime/elf_file.h +++ b/runtime/elf_file.h @@ -26,6 +26,9 @@ #include "./elf.h" namespace art { + +class MemMap; + template <typename ElfTypes> class ElfFileImpl; @@ -42,18 +45,21 @@ class ElfFile { bool writable, bool program_header_only, bool low_4gb, - std::string* error_msg, - uint8_t* requested_base = nullptr); // TODO: move arg to before error_msg. + /*out*/std::string* error_msg); // Open with specific mmap flags, Always maps in the whole file, not just the // program header sections. static ElfFile* Open(File* file, int mmap_prot, int mmap_flags, - std::string* error_msg); + /*out*/std::string* error_msg); ~ElfFile(); // Load segments into memory based on PT_LOAD program headers - bool Load(File* file, bool executable, bool low_4gb, std::string* error_msg); + bool Load(File* file, + bool executable, + bool low_4gb, + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg); const uint8_t* FindDynamicSymbolAddress(const std::string& symbol_name) const; diff --git a/runtime/elf_file_impl.h b/runtime/elf_file_impl.h index 58c38a4436..b55b60f2dc 100644 --- a/runtime/elf_file_impl.h +++ b/runtime/elf_file_impl.h @@ -48,13 +48,12 @@ class ElfFileImpl { bool writable, bool program_header_only, bool low_4gb, - std::string* error_msg, - uint8_t* requested_base = nullptr); + /*out*/std::string* error_msg); static ElfFileImpl* Open(File* file, int mmap_prot, int mmap_flags, bool low_4gb, - std::string* error_msg); + /*out*/std::string* error_msg); ~ElfFileImpl(); const std::string& GetFilePath() const { @@ -115,7 +114,11 @@ class ElfFileImpl { // Load segments into memory based on PT_LOAD program headers. // executable is true at run time, false at compile time. - bool Load(File* file, bool executable, bool low_4gb, std::string* error_msg); + bool Load(File* file, + bool executable, + bool low_4gb, + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg); bool Fixup(Elf_Addr base_address); bool FixupDynamic(Elf_Addr base_address); @@ -131,7 +134,11 @@ class ElfFileImpl { bool Strip(File* file, std::string* error_msg); private: - ElfFileImpl(File* file, bool writable, bool program_header_only, uint8_t* requested_base); + ElfFileImpl(File* file, bool writable, bool program_header_only); + + bool GetLoadedAddressRange(/*out*/uint8_t** vaddr_begin, + /*out*/size_t* vaddr_size, + /*out*/std::string* error_msg) const; bool Setup(File* file, int prot, int flags, bool low_4gb, std::string* error_msg); @@ -217,9 +224,6 @@ class ElfFileImpl { SymbolTable* symtab_symbol_table_; SymbolTable* dynsym_symbol_table_; - // Override the 'base' p_vaddr in the first LOAD segment with this value (if non-null). - uint8_t* requested_base_; - DISALLOW_COPY_AND_ASSIGN(ElfFileImpl); }; diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 3ea88d6ec7..b4453d97b2 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -890,6 +890,21 @@ void ConcurrentCopying::MarkingPhase() { } // Scan all of the objects on dirty cards in unevac from space, and non moving space. These // are from previous GCs and may reference things in the from space. + // + // Note that we do not need to process the large-object space (the only discontinuous space) + // as it contains only large string objects and large primitive array objects, that have no + // reference to other objects, except their class. There is no need to scan these large + // objects, as the String class and the primitive array classes are expected to never move + // during a minor (young-generation) collection: + // - In the case where we run with a boot image, these classes are part of the image space, + // which is an immune space. + // - In the case where we run without a boot image, these classes are allocated in the region + // space (main space), but they are not expected to move during a minor collection (this + // would only happen if those classes were allocated between a major and a minor + // collections, which is unlikely -- we don't expect any GC to happen before these + // fundamental classes are initialized). Note that these classes could move during a major + // collection though, but this is fine: in that case, the whole heap is traced and the card + // table logic below is not used. Runtime::Current()->GetHeap()->GetCardTable()->Scan<false>( space->GetMarkBitmap(), space->Begin(), @@ -2333,10 +2348,13 @@ void ConcurrentCopying::AssertToSpaceInvariantInNonMovingSpace(mirror::Object* o // If `ref` is on the allocation stack, then it may not be // marked live, but considered marked/alive (but not // necessarily on the live stack). - CHECK(IsOnAllocStack(ref)) << "Unmarked ref that's not on the allocation stack." - << " obj=" << obj - << " ref=" << ref - << " is_los=" << std::boolalpha << is_los << std::noboolalpha; + CHECK(IsOnAllocStack(ref)) + << "Unmarked ref that's not on the allocation stack." + << " obj=" << obj + << " ref=" << ref + << " is_los=" << std::boolalpha << is_los << std::noboolalpha + << " is_marking=" << std::boolalpha << is_marking_ << std::noboolalpha + << " young_gen=" << std::boolalpha << young_gen_ << std::noboolalpha; } } } diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc index 677e3f3a05..7bd87bda7a 100644 --- a/runtime/gc/collector/immune_spaces_test.cc +++ b/runtime/gc/collector/immune_spaces_test.cc @@ -41,13 +41,13 @@ class DummyOatFile : public OatFile { class DummyImageSpace : public space::ImageSpace { public: DummyImageSpace(MemMap&& map, - accounting::ContinuousSpaceBitmap* live_bitmap, + std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap, std::unique_ptr<DummyOatFile>&& oat_file, MemMap&& oat_map) : ImageSpace("DummyImageSpace", /*image_location*/"", std::move(map), - live_bitmap, + std::move(live_bitmap), map.End()), oat_map_(std::move(oat_map)) { oat_file_ = std::move(oat_file); @@ -130,7 +130,7 @@ class ImmuneSpacesTest : public CommonRuntimeTest { ImageHeader::kStorageModeUncompressed, /*storage_size*/0u); return new DummyImageSpace(std::move(map), - live_bitmap.release(), + std::move(live_bitmap), std::move(oat_file), std::move(oat_map)); } diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index b74071b8aa..589e9a4826 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -312,24 +312,63 @@ Heap::Heap(size_t initial_size, ChangeCollector(desired_collector_type_); live_bitmap_.reset(new accounting::HeapBitmap(this)); mark_bitmap_.reset(new accounting::HeapBitmap(this)); - // Requested begin for the alloc space, to follow the mapped image and oat files - uint8_t* requested_alloc_space_begin = nullptr; - if (foreground_collector_type_ == kCollectorTypeCC) { - // Need to use a low address so that we can allocate a contiguous 2 * Xmx space when there's no - // image (dex2oat for target). - requested_alloc_space_begin = kPreferredAllocSpaceBegin; + + // We don't have hspace compaction enabled with GSS or CC. + if (foreground_collector_type_ == kCollectorTypeGSS || + foreground_collector_type_ == kCollectorTypeCC) { + use_homogeneous_space_compaction_for_oom_ = false; + } + bool support_homogeneous_space_compaction = + background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact || + use_homogeneous_space_compaction_for_oom_; + // We may use the same space the main space for the non moving space if we don't need to compact + // from the main space. + // This is not the case if we support homogeneous compaction or have a moving background + // collector type. + bool separate_non_moving_space = is_zygote || + support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) || + IsMovingGc(background_collector_type_); + if (foreground_collector_type_ == kCollectorTypeGSS) { + separate_non_moving_space = false; } + // Requested begin for the alloc space, to follow the mapped image and oat files + uint8_t* request_begin = nullptr; + // Calculate the extra space required after the boot image, see allocations below. + size_t heap_reservation_size = separate_non_moving_space + ? non_moving_space_capacity + : ((is_zygote && foreground_collector_type_ != kCollectorTypeCC) ? capacity_ : 0u); + heap_reservation_size = RoundUp(heap_reservation_size, kPageSize); // Load image space(s). std::vector<std::unique_ptr<space::ImageSpace>> boot_image_spaces; + MemMap heap_reservation; if (space::ImageSpace::LoadBootImage(image_file_name, image_instruction_set, + heap_reservation_size, &boot_image_spaces, - &requested_alloc_space_begin)) { + &heap_reservation)) { + DCHECK_EQ(heap_reservation_size, heap_reservation.IsValid() ? heap_reservation.Size() : 0u); + DCHECK(!boot_image_spaces.empty()); + request_begin = boot_image_spaces.back()->GetImageHeader().GetOatFileEnd(); + DCHECK(!heap_reservation.IsValid() || request_begin == heap_reservation.Begin()) + << "request_begin=" << static_cast<const void*>(request_begin) + << " heap_reservation.Begin()=" << static_cast<const void*>(heap_reservation.Begin()); for (std::unique_ptr<space::ImageSpace>& space : boot_image_spaces) { boot_image_spaces_.push_back(space.get()); AddSpace(space.release()); } + } else { + if (foreground_collector_type_ == kCollectorTypeCC) { + // Need to use a low address so that we can allocate a contiguous 2 * Xmx space + // when there's no image (dex2oat for target). + request_begin = kPreferredAllocSpaceBegin; + } + // Gross hack to make dex2oat deterministic. + if (foreground_collector_type_ == kCollectorTypeMS && Runtime::Current()->IsAotCompiler()) { + // Currently only enabled for MS collector since that is what the deterministic dex2oat uses. + // b/26849108 + request_begin = reinterpret_cast<uint8_t*>(kAllocSpaceBeginForDeterministicAoT); + } } /* @@ -345,39 +384,10 @@ Heap::Heap(size_t initial_size, +-main alloc space2 / bump space 2 (capacity_)+- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- */ - // We don't have hspace compaction enabled with GSS or CC. - if (foreground_collector_type_ == kCollectorTypeGSS || - foreground_collector_type_ == kCollectorTypeCC) { - use_homogeneous_space_compaction_for_oom_ = false; - } - bool support_homogeneous_space_compaction = - background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact || - use_homogeneous_space_compaction_for_oom_; - // We may use the same space the main space for the non moving space if we don't need to compact - // from the main space. - // This is not the case if we support homogeneous compaction or have a moving background - // collector type. - bool separate_non_moving_space = is_zygote || - support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) || - IsMovingGc(background_collector_type_); - if (foreground_collector_type_ == kCollectorTypeGSS) { - separate_non_moving_space = false; - } + MemMap main_mem_map_1; MemMap main_mem_map_2; - // Gross hack to make dex2oat deterministic. - if (foreground_collector_type_ == kCollectorTypeMS && - requested_alloc_space_begin == nullptr && - Runtime::Current()->IsAotCompiler()) { - // Currently only enabled for MS collector since that is what the deterministic dex2oat uses. - // b/26849108 - requested_alloc_space_begin = reinterpret_cast<uint8_t*>(kAllocSpaceBeginForDeterministicAoT); - } - uint8_t* request_begin = requested_alloc_space_begin; - if (request_begin != nullptr && separate_non_moving_space) { - request_begin += non_moving_space_capacity; - } std::string error_str; MemMap non_moving_space_mem_map; if (separate_non_moving_space) { @@ -388,9 +398,16 @@ Heap::Heap(size_t initial_size, const char* space_name = is_zygote ? kZygoteSpaceName : kNonMovingSpaceName; // Reserve the non moving mem map before the other two since it needs to be at a specific // address. - non_moving_space_mem_map = MapAnonymousPreferredAddress( - space_name, requested_alloc_space_begin, non_moving_space_capacity, &error_str); + DCHECK_EQ(heap_reservation.IsValid(), !boot_image_spaces_.empty()); + if (heap_reservation.IsValid()) { + non_moving_space_mem_map = heap_reservation.RemapAtEnd( + heap_reservation.Begin(), space_name, PROT_READ | PROT_WRITE, &error_str); + } else { + non_moving_space_mem_map = MapAnonymousPreferredAddress( + space_name, request_begin, non_moving_space_capacity, &error_str); + } CHECK(non_moving_space_mem_map.IsValid()) << error_str; + DCHECK(!heap_reservation.IsValid()); // Try to reserve virtual memory at a lower address if we have a separate non moving space. request_begin = kPreferredAllocSpaceBegin + non_moving_space_capacity; } @@ -404,14 +421,19 @@ Heap::Heap(size_t initial_size, // If no separate non-moving space and we are the zygote, the main space must come right // after the image space to avoid a gap. This is required since we want the zygote space to // be adjacent to the image space. - main_mem_map_1 = MemMap::MapAnonymous(kMemMapSpaceName[0], - request_begin, - capacity_, - PROT_READ | PROT_WRITE, - /* low_4gb */ true, - &error_str); + DCHECK_EQ(heap_reservation.IsValid(), !boot_image_spaces_.empty()); + main_mem_map_1 = MemMap::MapAnonymous( + kMemMapSpaceName[0], + request_begin, + capacity_, + PROT_READ | PROT_WRITE, + /* low_4gb */ true, + /* reuse */ false, + heap_reservation.IsValid() ? &heap_reservation : nullptr, + &error_str); } CHECK(main_mem_map_1.IsValid()) << error_str; + DCHECK(!heap_reservation.IsValid()); } if (support_homogeneous_space_compaction || background_collector_type_ == kCollectorTypeSS || @@ -437,7 +459,7 @@ Heap::Heap(size_t initial_size, /* can_move_objects */ false); non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity()); CHECK(non_moving_space_ != nullptr) << "Failed creating non moving space " - << requested_alloc_space_begin; + << non_moving_space_mem_map.Begin(); AddSpace(non_moving_space_); } // Create other spaces based on whether or not we have a moving GC. diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 71786279e2..f308f63386 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -63,7 +63,7 @@ Atomic<uint32_t> ImageSpace::bitmap_index_(0); ImageSpace::ImageSpace(const std::string& image_filename, const char* image_location, MemMap&& mem_map, - accounting::ContinuousSpaceBitmap* live_bitmap, + std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap, uint8_t* end) : MemMapSpace(image_filename, std::move(mem_map), @@ -71,10 +71,10 @@ ImageSpace::ImageSpace(const std::string& image_filename, end, end, kGcRetentionPolicyNeverCollect), + live_bitmap_(std::move(live_bitmap)), oat_file_non_owned_(nullptr), image_location_(image_location) { - DCHECK(live_bitmap != nullptr); - live_bitmap_.reset(live_bitmap); + DCHECK(live_bitmap_ != nullptr); } static int32_t ChooseRelocationOffsetDelta(int32_t min_delta, int32_t max_delta) { @@ -480,52 +480,16 @@ std::ostream& operator<<(std::ostream& os, const RelocationRange& reloc) { } // Helper class encapsulating loading, so we can access private ImageSpace members (this is a -// friend class), but not declare functions in the header. +// nested class), but not declare functions in the header. class ImageSpace::Loader { public: - static std::unique_ptr<ImageSpace> Load(const std::string& image_location, - const std::string& image_filename, - bool is_zygote, - bool is_global_cache, - bool validate_oat_file, - std::string* error_msg) - REQUIRES_SHARED(Locks::mutator_lock_) { - // Should this be a RDWR lock? This is only a defensive measure, as at - // this point the image should exist. - // However, only the zygote can write into the global dalvik-cache, so - // restrict to zygote processes, or any process that isn't using - // /data/dalvik-cache (which we assume to be allowed to write there). - const bool rw_lock = is_zygote || !is_global_cache; - - // Note that we must not use the file descriptor associated with - // ScopedFlock::GetFile to Init the image file. We want the file - // descriptor (and the associated exclusive lock) to be released when - // we leave Create. - ScopedFlock image = LockedFile::Open(image_filename.c_str(), - rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */, - true /* block */, - error_msg); - - VLOG(startup) << "Using image file " << image_filename.c_str() << " for image location " - << image_location; - // If we are in /system we can assume the image is good. We can also - // assume this if we are using a relocated image (i.e. image checksum - // matches) since this is only different by the offset. We need this to - // make sure that host tests continue to work. - // Since we are the boot image, pass null since we load the oat file from the boot image oat - // file name. - return Init(image_filename.c_str(), - image_location.c_str(), - validate_oat_file, - /* oat_file */nullptr, - error_msg); - } - static std::unique_ptr<ImageSpace> Init(const char* image_filename, const char* image_location, bool validate_oat_file, const OatFile* oat_file, - std::string* error_msg) + /*inout*/MemMap* image_reservation, + /*inout*/MemMap* oat_reservation, + /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { CHECK(image_filename != nullptr); CHECK(image_location != nullptr); @@ -616,22 +580,27 @@ class ImageSpace::Loader { // image at the image begin, the amount of fixup work required is minimized. // If it is pic we will retry with error_msg for the failure case. Pass a null error_msg to // avoid reading proc maps for a mapping failure and slowing everything down. - map = LoadImageFile(image_filename, - image_location, - *image_header, - image_header->GetImageBegin(), - file->Fd(), - logger, - image_header->IsPic() ? nullptr : error_msg); + // For the boot image, we have already reserved the memory and we load the image + // into the `image_reservation`. + map = LoadImageFile( + image_filename, + image_location, + *image_header, + image_header->GetImageBegin(), + file->Fd(), + logger, + image_reservation, + (image_reservation == nullptr && image_header->IsPic()) ? nullptr : error_msg); // If the header specifies PIC mode, we can also map at a random low_4gb address since we can // relocate in-place. - if (!map.IsValid() && image_header->IsPic()) { + if (!map.IsValid() && image_reservation == nullptr && image_header->IsPic()) { map = LoadImageFile(image_filename, image_location, *image_header, /* address */ nullptr, file->Fd(), logger, + /* image_reservation */ nullptr, error_msg); } // Were we able to load something and continue? @@ -641,15 +610,13 @@ class ImageSpace::Loader { } DCHECK_EQ(0, memcmp(image_header, map.Begin(), sizeof(ImageHeader))); - MemMap image_bitmap_map = MemMap::MapFileAtAddress(nullptr, - bitmap_section.Size(), - PROT_READ, MAP_PRIVATE, - file->Fd(), - image_bitmap_offset, - /*low_4gb*/false, - /*reuse*/false, - image_filename, - error_msg); + MemMap image_bitmap_map = MemMap::MapFile(bitmap_section.Size(), + PROT_READ, MAP_PRIVATE, + file->Fd(), + image_bitmap_offset, + /* low_4gb */ false, + image_filename, + error_msg); if (!image_bitmap_map.IsValid()) { *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str()); return nullptr; @@ -694,7 +661,7 @@ class ImageSpace::Loader { std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, image_location, std::move(map), - bitmap.release(), + std::move(bitmap), image_end)); // VerifyImageAllocations() will be called later in Runtime::Init() @@ -704,7 +671,7 @@ class ImageSpace::Loader { // set yet at this point. if (oat_file == nullptr) { TimingLogger::ScopedTiming timing("OpenOatFile", &logger); - space->oat_file_ = OpenOatFile(*space, image_filename, error_msg); + space->oat_file_ = OpenOatFile(*space, image_filename, oat_reservation, error_msg); if (space->oat_file_ == nullptr) { DCHECK(!error_msg->empty()); return nullptr; @@ -787,7 +754,8 @@ class ImageSpace::Loader { uint8_t* address, int fd, TimingLogger& logger, - std::string* error_msg) { + /*inout*/MemMap* image_reservation, + /*out*/std::string* error_msg) { TimingLogger::ScopedTiming timing("MapImageFile", &logger); const ImageHeader::StorageMode storage_mode = image_header.GetStorageMode(); if (storage_mode == ImageHeader::kStorageModeUncompressed) { @@ -796,10 +764,11 @@ class ImageSpace::Loader { PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, - 0, - /*low_4gb*/true, - /*reuse*/false, + /* start */ 0, + /* low_4gb */ true, image_filename, + /* reuse */ false, + image_reservation, error_msg); } @@ -817,7 +786,9 @@ class ImageSpace::Loader { address, image_header.GetImageSize(), PROT_READ | PROT_WRITE, - /*low_4gb*/ true, + /* low_4gb */ true, + /* reuse */ false, + image_reservation, error_msg); if (map.IsValid()) { const size_t stored_size = image_header.GetDataSize(); @@ -826,8 +797,8 @@ class ImageSpace::Loader { PROT_READ, MAP_PRIVATE, fd, - /*offset*/0, - /*low_4gb*/false, + /* offset */ 0, + /* low_4gb */ false, image_filename, error_msg); if (!temp_map.IsValid()) { @@ -1396,6 +1367,7 @@ class ImageSpace::Loader { static std::unique_ptr<OatFile> OpenOatFile(const ImageSpace& image, const char* image_path, + /*inout*/MemMap* oat_reservation, std::string* error_msg) { const ImageHeader& image_header = image.GetImageHeader(); std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path); @@ -1406,10 +1378,10 @@ class ImageSpace::Loader { oat_filename, oat_filename, image_header.GetOatDataBegin(), - image_header.GetOatFileBegin(), !Runtime::Current()->IsAotCompiler(), - /*low_4gb*/false, - nullptr, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + oat_reservation, error_msg)); if (oat_file == nullptr) { *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s", @@ -1489,29 +1461,61 @@ class ImageSpace::BootImageLoader { return cache_filename_; } - bool LoadFromSystem(/*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, - /*out*/ uint8_t** oat_file_end, - /*out*/ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { + bool LoadFromSystem(size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { std::string filename = GetSystemImageFilename(image_location_.c_str(), image_isa_); std::vector<std::string> locations; if (!GetBootClassPathImageLocations(image_location_, filename, &locations, error_msg)) { return false; } + uint32_t image_start; + uint32_t image_end; + uint32_t oat_end; + if (!GetBootImageAddressRange(filename, &image_start, &image_end, &oat_end, error_msg)) { + return false; + } + if (locations.size() > 1u) { + std::string last_filename = GetSystemImageFilename(locations.back().c_str(), image_isa_); + uint32_t dummy; + if (!GetBootImageAddressRange(last_filename, &dummy, &image_end, &oat_end, error_msg)) { + return false; + } + } + MemMap image_reservation; + MemMap oat_reservation; + MemMap local_extra_reservation; + if (!ReserveBootImageMemory(image_start, + image_end, + oat_end, + extra_reservation_size, + &image_reservation, + &oat_reservation, + &local_extra_reservation, + error_msg)) { + return false; + } + std::vector<std::unique_ptr<ImageSpace>> spaces; spaces.reserve(locations.size()); for (const std::string& location : locations) { filename = GetSystemImageFilename(location.c_str(), image_isa_); - spaces.push_back(Loader::Load(location, - filename, - is_zygote_, - is_global_cache_, - /* validate_oat_file */ false, - error_msg)); + spaces.push_back(Load(location, + filename, + /* validate_oat_file */ false, + &image_reservation, + &oat_reservation, + error_msg)); if (spaces.back() == nullptr) { return false; } } - *oat_file_end = GetOatFileEnd(spaces); + if (!CheckReservationsExhausted(image_reservation, oat_reservation, error_msg)) { + return false; + } + + *extra_reservation = std::move(local_extra_reservation); boot_image_spaces->swap(spaces); return true; } @@ -1519,14 +1523,48 @@ class ImageSpace::BootImageLoader { bool LoadFromDalvikCache( bool validate_system_checksums, bool validate_oat_file, - /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, - /*out*/ uint8_t** oat_file_end, - /*out*/ std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { + size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(DalvikCacheExists()); std::vector<std::string> locations; if (!GetBootClassPathImageLocations(image_location_, cache_filename_, &locations, error_msg)) { return false; } + uint32_t image_start; + uint32_t image_end; + uint32_t oat_end; + if (!GetBootImageAddressRange(cache_filename_, &image_start, &image_end, &oat_end, error_msg)) { + return false; + } + if (locations.size() > 1u) { + std::string last_filename; + if (!GetDalvikCacheFilename(locations.back().c_str(), + dalvik_cache_.c_str(), + &last_filename, + error_msg)) { + return false; + } + uint32_t dummy; + if (!GetBootImageAddressRange(last_filename, &dummy, &image_end, &oat_end, error_msg)) { + return false; + } + } + MemMap image_reservation; + MemMap oat_reservation; + MemMap local_extra_reservation; + if (!ReserveBootImageMemory(image_start, + image_end, + oat_end, + extra_reservation_size, + &image_reservation, + &oat_reservation, + &local_extra_reservation, + error_msg)) { + return false; + } + std::vector<std::unique_ptr<ImageSpace>> spaces; spaces.reserve(locations.size()); for (const std::string& location : locations) { @@ -1534,12 +1572,12 @@ class ImageSpace::BootImageLoader { if (!GetDalvikCacheFilename(location.c_str(), dalvik_cache_.c_str(), &filename, error_msg)) { return false; } - spaces.push_back(Loader::Load(location, - filename, - is_zygote_, - is_global_cache_, - validate_oat_file, - error_msg)); + spaces.push_back(Load(location, + filename, + validate_oat_file, + &image_reservation, + &oat_reservation, + error_msg)); if (spaces.back() == nullptr) { return false; } @@ -1560,12 +1598,56 @@ class ImageSpace::BootImageLoader { } } } - *oat_file_end = GetOatFileEnd(spaces); + if (!CheckReservationsExhausted(image_reservation, oat_reservation, error_msg)) { + return false; + } + + *extra_reservation = std::move(local_extra_reservation); boot_image_spaces->swap(spaces); return true; } private: + std::unique_ptr<ImageSpace> Load(const std::string& image_location, + const std::string& image_filename, + bool validate_oat_file, + /*inout*/MemMap* image_reservation, + /*inout*/MemMap* oat_reservation, + /*out*/std::string* error_msg) + REQUIRES_SHARED(Locks::mutator_lock_) { + // Should this be a RDWR lock? This is only a defensive measure, as at + // this point the image should exist. + // However, only the zygote can write into the global dalvik-cache, so + // restrict to zygote processes, or any process that isn't using + // /data/dalvik-cache (which we assume to be allowed to write there). + const bool rw_lock = is_zygote_ || !is_global_cache_; + + // Note that we must not use the file descriptor associated with + // ScopedFlock::GetFile to Init the image file. We want the file + // descriptor (and the associated exclusive lock) to be released when + // we leave Create. + ScopedFlock image = LockedFile::Open(image_filename.c_str(), + rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY /* flags */, + true /* block */, + error_msg); + + VLOG(startup) << "Using image file " << image_filename.c_str() << " for image location " + << image_location; + // If we are in /system we can assume the image is good. We can also + // assume this if we are using a relocated image (i.e. image checksum + // matches) since this is only different by the offset. We need this to + // make sure that host tests continue to work. + // Since we are the boot image, pass null since we load the oat file from the boot image oat + // file name. + return Loader::Init(image_filename.c_str(), + image_location.c_str(), + validate_oat_file, + /* oat_file */ nullptr, + image_reservation, + oat_reservation, + error_msg); + } + // Extract boot class path from oat file associated with `image_filename` // and list all associated image locations. static bool GetBootClassPathImageLocations(const std::string& image_location, @@ -1577,10 +1659,10 @@ class ImageSpace::BootImageLoader { oat_filename, oat_filename, /* requested_base */ nullptr, - /* oat_file_begin */ nullptr, /* executable */ false, /* low_4gb */ false, /* abs_dex_location */ nullptr, + /* reservation */ nullptr, error_msg)); if (oat_file == nullptr) { *error_msg = StringPrintf("Failed to open oat file '%s' for image file %s: %s", @@ -1598,14 +1680,87 @@ class ImageSpace::BootImageLoader { return true; } - uint8_t* GetOatFileEnd(const std::vector<std::unique_ptr<ImageSpace>>& spaces) { - DCHECK(std::is_sorted( - spaces.begin(), - spaces.end(), - [](const std::unique_ptr<ImageSpace>& lhs, const std::unique_ptr<ImageSpace>& rhs) { - return lhs->GetOatFileEnd() < rhs->GetOatFileEnd(); - })); - return AlignUp(spaces.back()->GetOatFileEnd(), kPageSize); + bool GetBootImageAddressRange(const std::string& filename, + /*out*/uint32_t* start, + /*out*/uint32_t* end, + /*out*/uint32_t* oat_end, + /*out*/std::string* error_msg) { + ImageHeader system_hdr; + if (!ReadSpecificImageHeader(filename.c_str(), &system_hdr)) { + *error_msg = StringPrintf("Cannot read header of %s", filename.c_str()); + return false; + } + *start = reinterpret_cast32<uint32_t>(system_hdr.GetImageBegin()); + CHECK_ALIGNED(*start, kPageSize); + *end = RoundUp(*start + system_hdr.GetImageSize(), kPageSize); + *oat_end = RoundUp(reinterpret_cast32<uint32_t>(system_hdr.GetOatFileEnd()), kPageSize); + return true; + } + + bool ReserveBootImageMemory(uint32_t image_start, + uint32_t image_end, + uint32_t oat_end, + size_t extra_reservation_size, + /*out*/MemMap* image_reservation, + /*out*/MemMap* oat_reservation, + /*out*/MemMap* extra_reservation, + /*out*/std::string* error_msg) { + DCHECK(!image_reservation->IsValid()); + size_t total_size = + dchecked_integral_cast<size_t>(oat_end - image_start) + extra_reservation_size; + *image_reservation = + MemMap::MapAnonymous("Boot image reservation", + reinterpret_cast32<uint8_t*>(image_start), + total_size, + PROT_NONE, + /* low_4gb */ true, + /* reuse */ false, + /* reservation */ nullptr, + error_msg); + if (!image_reservation->IsValid()) { + 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()) { + return false; + } + } + DCHECK(!oat_reservation->IsValid()); + *oat_reservation = image_reservation->RemapAtEnd(reinterpret_cast32<uint8_t*>(image_end), + "Boot image oat reservation", + PROT_NONE, + error_msg); + if (!oat_reservation->IsValid()) { + return false; + } + + return true; + } + + bool CheckReservationsExhausted(const MemMap& image_reservation, + const MemMap& oat_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()); + return false; + } + if (oat_reservation.IsValid()) { + *error_msg = StringPrintf("Excessive oat reservation after loading boot image: %p-%p", + image_reservation.Begin(), + image_reservation.End()); + return false; + } + return true; } const std::string& image_location_; @@ -1657,13 +1812,15 @@ static bool CheckSpace(const std::string& cache_filename, std::string* error_msg bool ImageSpace::LoadBootImage( const std::string& image_location, const InstructionSet image_isa, - /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, - /*out*/ uint8_t** oat_file_end) { + size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation) { ScopedTrace trace(__FUNCTION__); DCHECK(boot_image_spaces != nullptr); DCHECK(boot_image_spaces->empty()); - DCHECK(oat_file_end != nullptr); + DCHECK_ALIGNED(extra_reservation_size, kPageSize); + DCHECK(extra_reservation != nullptr); DCHECK_NE(image_isa, InstructionSet::kNone); if (image_location.empty()) { @@ -1720,8 +1877,9 @@ bool ImageSpace::LoadBootImage( // If we have system image, validate system image checksums, otherwise validate the oat file. if (loader.LoadFromDalvikCache(/* validate_system_checksums */ loader.HasSystem(), /* validate_oat_file */ !loader.HasSystem(), + extra_reservation_size, boot_image_spaces, - oat_file_end, + extra_reservation, &local_error_msg)) { return true; } @@ -1735,7 +1893,10 @@ bool ImageSpace::LoadBootImage( if (loader.HasSystem() && !relocate) { std::string local_error_msg; - if (loader.LoadFromSystem(boot_image_spaces, oat_file_end, &local_error_msg)) { + if (loader.LoadFromSystem(extra_reservation_size, + boot_image_spaces, + extra_reservation, + &local_error_msg)) { return true; } error_msgs.push_back(local_error_msg); @@ -1752,8 +1913,9 @@ bool ImageSpace::LoadBootImage( if (patch_success) { if (loader.LoadFromDalvikCache(/* validate_system_checksums */ false, /* validate_oat_file */ false, + extra_reservation_size, boot_image_spaces, - oat_file_end, + extra_reservation, &local_error_msg)) { return true; } @@ -1777,8 +1939,9 @@ bool ImageSpace::LoadBootImage( if (compilation_success) { if (loader.LoadFromDalvikCache(/* validate_system_checksums */ false, /* validate_oat_file */ false, + extra_reservation_size, boot_image_spaces, - oat_file_end, + extra_reservation, &local_error_msg)) { return true; } @@ -1834,7 +1997,13 @@ ImageSpace::~ImageSpace() { std::unique_ptr<ImageSpace> ImageSpace::CreateFromAppImage(const char* image, const OatFile* oat_file, std::string* error_msg) { - return Loader::Init(image, image, /*validate_oat_file*/false, oat_file, /*out*/error_msg); + return Loader::Init(image, + image, + /* validate_oat_file */ false, + oat_file, + /* image_reservation */ nullptr, + /* oat_reservation */ nullptr, + error_msg); } const OatFile* ImageSpace::GetOatFile() const { diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h index 93cf947218..a2490acdbb 100644 --- a/runtime/gc/space/image_space.h +++ b/runtime/gc/space/image_space.h @@ -44,8 +44,9 @@ class ImageSpace : public MemMapSpace { static bool LoadBootImage( const std::string& image_location, const InstructionSet image_isa, - /*out*/ std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, - /*out*/ uint8_t** oat_file_end) REQUIRES_SHARED(Locks::mutator_lock_); + size_t extra_reservation_size, + /*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces, + /*out*/MemMap* extra_reservation) REQUIRES_SHARED(Locks::mutator_lock_); // Try to open an existing app image space. static std::unique_ptr<ImageSpace> CreateFromAppImage(const char* image, @@ -183,7 +184,7 @@ class ImageSpace : public MemMapSpace { ImageSpace(const std::string& name, const char* image_location, MemMap&& mem_map, - accounting::ContinuousSpaceBitmap* live_bitmap, + std::unique_ptr<accounting::ContinuousSpaceBitmap> live_bitmap, uint8_t* end); // The OatFile associated with the image during early startup to diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc index d93385de3a..299a413432 100644 --- a/runtime/gc/space/image_space_test.cc +++ b/runtime/gc/space/image_space_test.cc @@ -41,16 +41,16 @@ TEST_F(DexoptTest, ValidateOatFile) { args.push_back("--dex-file=" + multidex1); args.push_back("--dex-file=" + dex2); args.push_back("--oat-file=" + oat_location); - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; std::unique_ptr<OatFile> oat(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - nullptr, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, + /* abs_dex_location */ nullptr, + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(oat != nullptr) << error_msg; diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc index 69dae31b37..17b3cd45aa 100644 --- a/runtime/interpreter/interpreter_intrinsics.cc +++ b/runtime/interpreter/interpreter_intrinsics.cc @@ -116,10 +116,10 @@ UNARY_INTRINSIC(MterpLongNumberOfLeadingZeros, JAVASTYLE_CLZ, GetVRegLong, SetJ) UNARY_INTRINSIC(MterpLongNumberOfTrailingZeros, JAVASTYLE_CTZ, GetVRegLong, SetJ); // java.lang.Long.rotateRight(JI)J -BINARY_JJ_INTRINSIC(MterpLongRotateRight, (Rot<int64_t, false>), SetJ); +BINARY_JI_INTRINSIC(MterpLongRotateRight, (Rot<int64_t, false>), SetJ); // java.lang.Long.rotateLeft(JI)J -BINARY_JJ_INTRINSIC(MterpLongRotateLeft, (Rot<int64_t, true>), SetJ); +BINARY_JI_INTRINSIC(MterpLongRotateLeft, (Rot<int64_t, true>), SetJ); // java.lang.Long.signum(J)I UNARY_INTRINSIC(MterpLongSignum, Signum, GetVRegLong, SetI); diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h index f76b86c94f..0e4cf27e50 100644 --- a/runtime/interpreter/shadow_frame.h +++ b/runtime/interpreter/shadow_frame.h @@ -159,14 +159,14 @@ class ShadowFrame { } int64_t GetVRegLong(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); + DCHECK_LT(i + 1, NumberOfVRegs()); const uint32_t* vreg = &vregs_[i]; typedef const int64_t unaligned_int64 __attribute__ ((aligned (4))); return *reinterpret_cast<unaligned_int64*>(vreg); } double GetVRegDouble(size_t i) const { - DCHECK_LT(i, NumberOfVRegs()); + DCHECK_LT(i + 1, NumberOfVRegs()); const uint32_t* vreg = &vregs_[i]; typedef const double unaligned_double __attribute__ ((aligned (4))); return *reinterpret_cast<unaligned_double*>(vreg); @@ -220,7 +220,7 @@ class ShadowFrame { } void SetVRegLong(size_t i, int64_t val) { - DCHECK_LT(i, NumberOfVRegs()); + DCHECK_LT(i + 1, NumberOfVRegs()); uint32_t* vreg = &vregs_[i]; typedef int64_t unaligned_int64 __attribute__ ((aligned (4))); *reinterpret_cast<unaligned_int64*>(vreg) = val; @@ -233,7 +233,7 @@ class ShadowFrame { } void SetVRegDouble(size_t i, double val) { - DCHECK_LT(i, NumberOfVRegs()); + DCHECK_LT(i + 1, NumberOfVRegs()); uint32_t* vreg = &vregs_[i]; typedef double unaligned_double __attribute__ ((aligned (4))); *reinterpret_cast<unaligned_double*>(vreg) = val; diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 33adc18673..2b2898c195 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -168,11 +168,6 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity, ScopedTrace trace(__PRETTY_FUNCTION__); CHECK_GE(max_capacity, initial_capacity); - // Generating debug information is for using the Linux perf tool on - // host which does not work with ashmem. - // Also, targets linux and fuchsia do not support ashmem. - bool use_ashmem = !generate_debug_info && !kIsTargetLinux && !kIsTargetFuchsia; - // With 'perf', we want a 1-1 mapping between an address and a method. // We aren't able to keep method pointers live during the instrumentation method entry trampoline // so we will just disable jit-gc if we are doing that. @@ -212,8 +207,8 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity, kProtData, /* low_4gb */ true, /* reuse */ false, - &error_str, - use_ashmem); + /* reservation */ nullptr, + &error_str); if (!data_map.IsValid()) { std::ostringstream oss; oss << "Failed to create read write cache: " << error_str << " size=" << max_capacity; @@ -233,7 +228,7 @@ JitCodeCache* JitCodeCache::Create(size_t initial_capacity, uint8_t* divider = data_map.Begin() + data_size; MemMap code_map = data_map.RemapAtEnd( - divider, "jit-code-cache", memmap_flags_prot_code | PROT_WRITE, &error_str, use_ashmem); + divider, "jit-code-cache", memmap_flags_prot_code | PROT_WRITE, &error_str); if (!code_map.IsValid()) { std::ostringstream oss; oss << "Failed to create read write execute cache: " << error_str << " size=" << max_capacity; diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 72eced2333..02aa1a823a 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -1401,7 +1401,10 @@ void Monitor::VisitLocks(StackVisitor* stack_visitor, void (*callback)(mirror::O // Ask the verifier for the dex pcs of all the monitor-enter instructions corresponding to // the locks held in this stack frame. std::vector<verifier::MethodVerifier::DexLockInfo> monitor_enter_dex_pcs; - verifier::MethodVerifier::FindLocksAtDexPc(m, dex_pc, &monitor_enter_dex_pcs); + verifier::MethodVerifier::FindLocksAtDexPc(m, + dex_pc, + &monitor_enter_dex_pcs, + Runtime::Current()->GetTargetSdkVersion()); for (verifier::MethodVerifier::DexLockInfo& dex_lock_info : monitor_enter_dex_pcs) { // As a debug check, check that dex PC corresponds to a monitor-enter. if (kIsDebugBuild) { diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 4780aea003..8f84a4bc19 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -102,12 +102,12 @@ class OatFileBase : public OatFile { const std::string& elf_filename, const std::string& location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); template <typename kOatFileBaseSubType> static OatFileBase* OpenOatFile(int zip_fd, @@ -116,12 +116,12 @@ class OatFileBase : public OatFile { const std::string& vdex_filename, const std::string& oat_filename, uint8_t* requested_base, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); protected: OatFileBase(const std::string& filename, bool executable) : OatFile(filename, executable) {} @@ -143,18 +143,18 @@ class OatFileBase : public OatFile { std::string* error_msg); virtual bool Load(const std::string& elf_filename, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, - std::string* error_msg) = 0; + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) = 0; virtual bool Load(int oat_fd, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, - std::string* error_msg) = 0; + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) = 0; bool ComputeFields(uint8_t* requested_base, const std::string& file_path, @@ -188,21 +188,21 @@ OatFileBase* OatFileBase::OpenOatFile(int zip_fd, const std::string& elf_filename, const std::string& location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(location, executable)); ret->PreLoad(); if (!ret->Load(elf_filename, - oat_file_begin, writable, executable, low_4gb, + reservation, error_msg)) { return nullptr; } @@ -231,19 +231,19 @@ OatFileBase* OatFileBase::OpenOatFile(int zip_fd, const std::string& vdex_location, const std::string& oat_location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(oat_location, executable)); if (!ret->Load(oat_fd, - oat_file_begin, writable, executable, low_4gb, + reservation, error_msg)) { return nullptr; } @@ -923,13 +923,18 @@ class DlOpenOatFile final : public OatFileBase { void PreLoad() override; bool Load(const std::string& elf_filename, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, - std::string* error_msg) override; - - bool Load(int, uint8_t*, bool, bool, bool, std::string*) { + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) override; + + bool Load(int oat_fd ATTRIBUTE_UNUSED, + bool writable ATTRIBUTE_UNUSED, + bool executable ATTRIBUTE_UNUSED, + bool low_4gb ATTRIBUTE_UNUSED, + /*inout*/MemMap* reservation ATTRIBUTE_UNUSED, + /*out*/std::string* error_msg ATTRIBUTE_UNUSED) override { return false; } @@ -938,8 +943,8 @@ class DlOpenOatFile final : public OatFileBase { private: bool Dlopen(const std::string& elf_filename, - uint8_t* oat_file_begin, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); // On the host, if the same library is loaded again with dlopen the same // file handle is returned. This differs from the behavior of dlopen on the @@ -952,12 +957,13 @@ class DlOpenOatFile final : public OatFileBase { // Guarded by host_dlopen_handles_lock_; static std::unordered_set<void*> host_dlopen_handles_; + // Reservation and dummy memory map objects corresponding to the regions mapped by dlopen. + // Note: Must be destroyed after dlclose() as it can hold the owning reservation. + std::vector<MemMap> dlopen_mmaps_; + // dlopen handle during runtime. void* dlopen_handle_; // TODO: Unique_ptr with custom deleter. - // Dummy memory map objects corresponding to the regions mapped by dlopen. - std::vector<MemMap> dlopen_mmaps_; - // The number of shared objects the linker told us about before loading. Used to // (optimistically) optimize the PreSetup stage (see comment there). size_t shared_objects_before_; @@ -975,9 +981,9 @@ void DlOpenOatFile::PreLoad() { #else // Count the entries in dl_iterate_phdr we get at this point in time. struct dl_iterate_context { - static int callback(struct dl_phdr_info *info ATTRIBUTE_UNUSED, + static int callback(dl_phdr_info* info ATTRIBUTE_UNUSED, size_t size ATTRIBUTE_UNUSED, - void *data) { + void* data) { reinterpret_cast<dl_iterate_context*>(data)->count++; return 0; // Continue iteration. } @@ -990,11 +996,11 @@ void DlOpenOatFile::PreLoad() { } bool DlOpenOatFile::Load(const std::string& elf_filename, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, - std::string* error_msg) { + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) { // Use dlopen only when flagged to do so, and when it's OK to load things executable. // TODO: Also try when not executable? The issue here could be re-mapping as writable (as // !executable is a sign that we may want to patch), which may not be allowed for @@ -1027,19 +1033,19 @@ bool DlOpenOatFile::Load(const std::string& elf_filename, } } - bool success = Dlopen(elf_filename, oat_file_begin, error_msg); + bool success = Dlopen(elf_filename, reservation, error_msg); DCHECK(dlopen_handle_ != nullptr || !success); return success; } bool DlOpenOatFile::Dlopen(const std::string& elf_filename, - uint8_t* oat_file_begin, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { #ifdef __APPLE__ // The dl_iterate_phdr syscall is missing. There is similar API on OSX, // but let's fallback to the custom loading code for the time being. - UNUSED(elf_filename, oat_file_begin); + UNUSED(elf_filename, reservation); *error_msg = "Dlopen unsupported on Mac."; return false; #else @@ -1056,15 +1062,85 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, // times). ANDROID_DLEXT_FORCE_FIXED_VADDR; // Take a non-zero vaddr as absolute // (non-pic boot image). - if (oat_file_begin != nullptr) { // - extinfo.flags |= ANDROID_DLEXT_LOAD_AT_FIXED_ADDRESS; // Use the requested addr if - extinfo.reserved_addr = oat_file_begin; // vaddr = 0. - } // (pic boot image). + if (reservation != nullptr) { + if (!reservation->IsValid()) { + *error_msg = StringPrintf("Invalid reservation for %s", elf_filename.c_str()); + return false; + } + extinfo.flags |= ANDROID_DLEXT_RESERVED_ADDRESS; // Use the reserved memory range. + extinfo.reserved_addr = reservation->Begin(); + extinfo.reserved_size = reservation->Size(); + } dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo); + if (reservation != nullptr && dlopen_handle_ != nullptr) { + // Find used pages from the reservation. + struct dl_iterate_context { + static int callback(dl_phdr_info* info, size_t size ATTRIBUTE_UNUSED, void* data) { + auto* context = reinterpret_cast<dl_iterate_context*>(data); + static_assert(std::is_same<Elf32_Half, Elf64_Half>::value, "Half must match"); + using Elf_Half = Elf64_Half; + + // See whether this callback corresponds to the file which we have just loaded. + uint8_t* reservation_begin = context->reservation->Begin(); + bool contained_in_reservation = false; + for (Elf_Half i = 0; i < info->dlpi_phnum; i++) { + if (info->dlpi_phdr[i].p_type == PT_LOAD) { + uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr + + info->dlpi_phdr[i].p_vaddr); + size_t memsz = info->dlpi_phdr[i].p_memsz; + size_t offset = static_cast<size_t>(vaddr - reservation_begin); + if (offset < context->reservation->Size()) { + contained_in_reservation = true; + DCHECK_LE(memsz, context->reservation->Size() - offset); + } else if (vaddr < reservation_begin) { + // Check that there's no overlap with the reservation. + DCHECK_LE(memsz, static_cast<size_t>(reservation_begin - vaddr)); + } + break; // It is sufficient to check the first PT_LOAD header. + } + } + + if (contained_in_reservation) { + for (Elf_Half i = 0; i < info->dlpi_phnum; i++) { + if (info->dlpi_phdr[i].p_type == PT_LOAD) { + uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr + + info->dlpi_phdr[i].p_vaddr); + size_t memsz = info->dlpi_phdr[i].p_memsz; + size_t offset = static_cast<size_t>(vaddr - reservation_begin); + DCHECK_LT(offset, context->reservation->Size()); + DCHECK_LE(memsz, context->reservation->Size() - offset); + context->max_size = std::max(context->max_size, offset + memsz); + } + } + + return 1; // Stop iteration and return 1 from dl_iterate_phdr. + } + return 0; // Continue iteration and return 0 from dl_iterate_phdr when finished. + } + + const MemMap* const reservation; + size_t max_size = 0u; + }; + dl_iterate_context context = { reservation }; + + if (dl_iterate_phdr(dl_iterate_context::callback, &context) == 0) { + LOG(FATAL) << "Could not find the shared object mmapped to the reservation."; + UNREACHABLE(); + } + + // Take ownership of the memory used by the shared object. dlopen() does not assume + // full ownership of this memory and dlclose() shall just remap it as zero pages with + // PROT_NONE. We need to unmap the memory when destroying this oat file. + dlopen_mmaps_.push_back(reservation->TakeReservedMemory(context.max_size)); + } #else - UNUSED(oat_file_begin); static_assert(!kIsTargetBuild || kIsTargetLinux || kIsTargetFuchsia, "host_dlopen_handles_ will leak handles"); + if (reservation != nullptr) { + *error_msg = StringPrintf("dlopen() into reserved memory is unsupported on host for '%s'.", + elf_filename.c_str()); + return false; + } MutexLock mu(Thread::Current(), *Locks::host_dlopen_handles_lock_); dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW); if (dlopen_handle_ != nullptr) { @@ -1092,8 +1168,11 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { UNREACHABLE(); #else struct dl_iterate_context { - static int callback(struct dl_phdr_info *info, size_t /* size */, void *data) { + static int callback(dl_phdr_info* info, size_t size ATTRIBUTE_UNUSED, void* data) { auto* context = reinterpret_cast<dl_iterate_context*>(data); + static_assert(std::is_same<Elf32_Half, Elf64_Half>::value, "Half must match"); + using Elf_Half = Elf64_Half; + context->shared_objects_seen++; if (context->shared_objects_seen < context->shared_objects_before) { // We haven't been called yet for anything we haven't seen before. Just continue. @@ -1104,7 +1183,7 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { // See whether this callback corresponds to the file which we have just loaded. bool contains_begin = false; - for (int i = 0; i < info->dlpi_phnum; i++) { + for (Elf_Half i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type == PT_LOAD) { uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr); @@ -1117,7 +1196,7 @@ void DlOpenOatFile::PreSetup(const std::string& elf_filename) { } // Add dummy mmaps for this file. if (contains_begin) { - for (int i = 0; i < info->dlpi_phnum; i++) { + for (Elf_Half i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type == PT_LOAD) { uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr); @@ -1163,13 +1242,12 @@ class ElfOatFile final : public OatFileBase { static ElfOatFile* OpenElfFile(int zip_fd, File* file, const std::string& location, - uint8_t* requested_base, - uint8_t* oat_file_begin, // Override base if not null bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); bool InitializeFromElfFile(int zip_fd, ElfFile* elf_file, @@ -1191,29 +1269,29 @@ class ElfOatFile final : public OatFileBase { } bool Load(const std::string& elf_filename, - uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, bool executable, bool low_4gb, - std::string* error_msg) override; + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) override; bool Load(int oat_fd, - uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, bool executable, bool low_4gb, - std::string* error_msg) override; + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) override; void PreSetup(const std::string& elf_filename ATTRIBUTE_UNUSED) override { } private: bool ElfFileOpen(File* file, - uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, bool executable, bool low_4gb, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); private: // Backing memory map for oat file during cross compilation. @@ -1225,20 +1303,19 @@ class ElfOatFile final : public OatFileBase { ElfOatFile* ElfOatFile::OpenElfFile(int zip_fd, File* file, const std::string& location, - uint8_t* requested_base, - uint8_t* oat_file_begin, // Override base if not null bool writable, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg) { + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg) { ScopedTrace trace("Open elf file " + location); std::unique_ptr<ElfOatFile> oat_file(new ElfOatFile(location, executable)); bool success = oat_file->ElfFileOpen(file, - oat_file_begin, writable, low_4gb, executable, + reservation, error_msg); if (!success) { CHECK(!error_msg->empty()); @@ -1246,7 +1323,7 @@ ElfOatFile* ElfOatFile::OpenElfFile(int zip_fd, } // Complete the setup. - if (!oat_file->ComputeFields(requested_base, file->GetPath(), error_msg)) { + if (!oat_file->ComputeFields(/* requested_base */ nullptr, file->GetPath(), error_msg)) { return nullptr; } @@ -1279,11 +1356,11 @@ bool ElfOatFile::InitializeFromElfFile(int zip_fd, } bool ElfOatFile::Load(const std::string& elf_filename, - uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, bool executable, bool low_4gb, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { ScopedTrace trace(__PRETTY_FUNCTION__); std::unique_ptr<File> file(OS::OpenFileForReading(elf_filename.c_str())); if (file == nullptr) { @@ -1291,57 +1368,56 @@ bool ElfOatFile::Load(const std::string& elf_filename, return false; } return ElfOatFile::ElfFileOpen(file.get(), - oat_file_begin, writable, executable, low_4gb, + reservation, error_msg); } bool ElfOatFile::Load(int oat_fd, - uint8_t* oat_file_begin, // Override where the file is loaded to if not null bool writable, bool executable, bool low_4gb, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { ScopedTrace trace(__PRETTY_FUNCTION__); if (oat_fd != -1) { - std::unique_ptr<File> file = std::make_unique<File>(oat_fd, false); - file->DisableAutoClose(); + int duped_fd = DupCloexec(oat_fd); + std::unique_ptr<File> file = std::make_unique<File>(duped_fd, false); if (file == nullptr) { *error_msg = StringPrintf("Failed to open oat filename for reading: %s", strerror(errno)); return false; } return ElfOatFile::ElfFileOpen(file.get(), - oat_file_begin, writable, executable, low_4gb, + reservation, error_msg); } return false; } bool ElfOatFile::ElfFileOpen(File* file, - uint8_t* oat_file_begin, bool writable, bool executable, bool low_4gb, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { ScopedTrace trace(__PRETTY_FUNCTION__); // TODO: rename requested_base to oat_data_begin elf_file_.reset(ElfFile::Open(file, writable, /*program_header_only*/true, low_4gb, - error_msg, - oat_file_begin)); + error_msg)); if (elf_file_ == nullptr) { DCHECK(!error_msg->empty()); return false; } - bool loaded = elf_file_->Load(file, executable, low_4gb, error_msg); + bool loaded = elf_file_->Load(file, executable, low_4gb, reservation, error_msg); DCHECK(loaded || !error_msg->empty()); return loaded; } @@ -1392,11 +1468,11 @@ OatFile* OatFile::Open(int zip_fd, const std::string& oat_filename, const std::string& oat_location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { ScopedTrace trace("Open oat file " + oat_location); CHECK(!oat_filename.empty()) << oat_location; CheckLocation(oat_location); @@ -1419,11 +1495,11 @@ OatFile* OatFile::Open(int zip_fd, oat_filename, oat_location, requested_base, - oat_file_begin, false /* writable */, executable, low_4gb, abs_dex_location, + reservation, error_msg); if (with_dlopen != nullptr) { return with_dlopen; @@ -1449,11 +1525,11 @@ OatFile* OatFile::Open(int zip_fd, oat_filename, oat_location, requested_base, - oat_file_begin, false /* writable */, executable, low_4gb, abs_dex_location, + reservation, error_msg); return with_internal; } @@ -1463,11 +1539,11 @@ OatFile* OatFile::Open(int zip_fd, int oat_fd, const std::string& oat_location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg) { + /*inout*/MemMap* reservation, + /*out*/std::string* error_msg) { CHECK(!oat_location.empty()) << oat_location; std::string vdex_location = GetVdexFilename(oat_location); @@ -1478,11 +1554,11 @@ OatFile* OatFile::Open(int zip_fd, vdex_location, oat_location, requested_base, - oat_file_begin, false /* writable */, executable, low_4gb, abs_dex_location, + reservation, error_msg); return with_internal; } @@ -1496,12 +1572,11 @@ OatFile* OatFile::OpenWritable(int zip_fd, return ElfOatFile::OpenElfFile(zip_fd, file, location, - nullptr, - nullptr, - true, - false, + /* writable */ true, + /* executable */ false, /*low_4gb*/false, abs_dex_location, + /* reservation */ nullptr, error_msg); } @@ -1514,12 +1589,11 @@ OatFile* OatFile::OpenReadable(int zip_fd, return ElfOatFile::OpenElfFile(zip_fd, file, location, - nullptr, - nullptr, - false, - false, + /* writable */ false, + /* executable */ false, /*low_4gb*/false, abs_dex_location, + /* reservation */ nullptr, error_msg); } diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 21e214408d..f20c603bf2 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -86,11 +86,11 @@ class OatFile { const std::string& filename, const std::string& location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); // Similar to OatFile::Open(const std::string...), but accepts input vdex and // odex files as file descriptors. We also take zip_fd in case the vdex does not @@ -100,11 +100,11 @@ class OatFile { int oat_fd, const std::string& oat_location, uint8_t* requested_base, - uint8_t* oat_file_begin, bool executable, bool low_4gb, const char* abs_dex_location, - std::string* error_msg); + /*inout*/MemMap* reservation, // Where to load if not null. + /*out*/std::string* error_msg); // Open an oat file from an already opened File. // Does not use dlopen underneath so cannot be used for runtime use diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index f7c74cc23b..4ed7e35eee 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -36,7 +36,6 @@ #include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" -#include "hidden_api.h" #include "image.h" #include "oat.h" #include "runtime.h" @@ -182,30 +181,6 @@ bool OatFileAssistant::IsInBootClassPath() { return false; } -bool OatFileAssistant::Lock(std::string* error_msg) { - CHECK(error_msg != nullptr); - CHECK(flock_.get() == nullptr) << "OatFileAssistant::Lock already acquired"; - - // Note the lock will only succeed for secondary dex files and in test - // environment. - // - // The lock *will fail* for all primary apks in a production environment. - // The app does not have permissions to create locks next to its dex location - // (be it system, data or vendor parition). We also cannot use the odex or - // oat location for the same reasoning. - // - // This is best effort and if it fails it's unlikely that we will be able - // to generate oat files anyway. - std::string lock_file_name = dex_location_ + "." + GetInstructionSetString(isa_) + ".flock"; - - flock_ = LockedFile::Open(lock_file_name.c_str(), error_msg); - if (flock_.get() == nullptr) { - unlink(lock_file_name.c_str()); - return false; - } - return true; -} - int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, bool profile_changed, bool downgrade, @@ -221,72 +196,10 @@ int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, return -dexopt_needed; } -// Figure out the currently specified compile filter option in the runtime. -// Returns true on success, false if the compiler filter is invalid, in which -// case error_msg describes the problem. -static bool GetRuntimeCompilerFilterOption(CompilerFilter::Filter* filter, - std::string* error_msg) { - CHECK(filter != nullptr); - CHECK(error_msg != nullptr); - - *filter = OatFileAssistant::kDefaultCompilerFilterForDexLoading; - for (StringPiece option : Runtime::Current()->GetCompilerOptions()) { - if (option.starts_with("--compiler-filter=")) { - const char* compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); - if (!CompilerFilter::ParseCompilerFilter(compiler_filter_string, filter)) { - *error_msg = std::string("Unknown --compiler-filter value: ") - + std::string(compiler_filter_string); - return false; - } - } - } - return true; -} - bool OatFileAssistant::IsUpToDate() { return GetBestInfo().Status() == kOatUpToDate; } -OatFileAssistant::ResultOfAttemptToUpdate -OatFileAssistant::MakeUpToDate(bool profile_changed, - ClassLoaderContext* class_loader_context, - std::string* error_msg) { - // The method doesn't use zip_fd_ and directly opens dex files at dex_locations_. - CHECK_EQ(-1, zip_fd_) << "MakeUpToDate should not be called with zip_fd"; - - CompilerFilter::Filter target; - if (!GetRuntimeCompilerFilterOption(&target, error_msg)) { - return kUpdateNotAttempted; - } - - OatFileInfo& info = GetBestInfo(); - // TODO(calin, jeffhao): the context should really be passed to GetDexOptNeeded: b/62269291. - // This is actually not trivial in the current logic as it will interact with the collision - // check: - // - currently, if the context does not match but we have no collisions we still accept the - // oat file. - // - if GetDexOptNeeded would return kDex2OatFromScratch for a context mismatch and we make - // the oat code up to date the collision check becomes useless. - // - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files - // loaded in other processes). So it boils down to how far do we want to complicate - // the logic in order to enable the use of oat files. Maybe its time to try simplify it. - switch (info.GetDexOptNeeded( - target, profile_changed, /*downgrade*/ false, class_loader_context)) { - case kNoDexOptNeeded: - return kUpdateSucceeded; - - // TODO: For now, don't bother with all the different ways we can call - // dex2oat to generate the oat file. Always generate the oat file as if it - // were kDex2OatFromScratch. - case kDex2OatFromScratch: - case kDex2OatForBootImage: - case kDex2OatForRelocation: - case kDex2OatForFilter: - return GenerateOatFileNoChecks(info, target, class_loader_context, error_msg); - } - UNREACHABLE(); -} - std::unique_ptr<OatFile> OatFileAssistant::GetBestOatFile() { return GetBestInfo().ReleaseFileForUse(); } @@ -615,243 +528,6 @@ static bool DexLocationToOdexNames(const std::string& location, return true; } -// Prepare a subcomponent of the odex directory. -// (i.e. create and set the expected permissions on the path `dir`). -static bool PrepareDirectory(const std::string& dir, std::string* error_msg) { - struct stat dir_stat; - if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &dir_stat)) == 0) { - // The directory exists. Check if it is indeed a directory. - if (!S_ISDIR(dir_stat.st_mode)) { - *error_msg = dir + " is not a dir"; - return false; - } else { - // The dir is already on disk. - return true; - } - } - - // Failed to stat. We need to create the directory. - if (errno != ENOENT) { - *error_msg = "Could not stat isa dir " + dir + ":" + strerror(errno); - return false; - } - - mode_t mode = S_IRWXU | S_IXGRP | S_IXOTH; - if (mkdir(dir.c_str(), mode) != 0) { - *error_msg = "Could not create dir " + dir + ":" + strerror(errno); - return false; - } - if (chmod(dir.c_str(), mode) != 0) { - *error_msg = "Could not create the oat dir " + dir + ":" + strerror(errno); - return false; - } - return true; -} - -// Prepares the odex directory for the given dex location. -static bool PrepareOdexDirectories(const std::string& dex_location, - const std::string& expected_odex_location, - InstructionSet isa, - std::string* error_msg) { - std::string actual_odex_location; - std::string oat_dir; - std::string isa_dir; - if (!DexLocationToOdexNames( - dex_location, isa, &actual_odex_location, &oat_dir, &isa_dir, error_msg)) { - return false; - } - DCHECK_EQ(expected_odex_location, actual_odex_location); - - if (!PrepareDirectory(oat_dir, error_msg)) { - return false; - } - if (!PrepareDirectory(isa_dir, error_msg)) { - return false; - } - return true; -} - -class Dex2oatFileWrapper { - public: - explicit Dex2oatFileWrapper(File* file) - : file_(file), - unlink_file_at_destruction_(true) { - } - - ~Dex2oatFileWrapper() { - if (unlink_file_at_destruction_ && (file_ != nullptr)) { - file_->Erase(/*unlink*/ true); - } - } - - File* GetFile() { return file_.get(); } - - void DisableUnlinkAtDestruction() { - unlink_file_at_destruction_ = false; - } - - private: - std::unique_ptr<File> file_; - bool unlink_file_at_destruction_; -}; - -OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks( - OatFileAssistant::OatFileInfo& info, - CompilerFilter::Filter filter, - const ClassLoaderContext* class_loader_context, - std::string* error_msg) { - CHECK(error_msg != nullptr); - - Runtime* runtime = Runtime::Current(); - if (!runtime->IsDex2OatEnabled()) { - *error_msg = "Generation of oat file for dex location " + dex_location_ - + " not attempted because dex2oat is disabled."; - return kUpdateNotAttempted; - } - - if (info.Filename() == nullptr) { - *error_msg = "Generation of oat file for dex location " + dex_location_ - + " not attempted because the oat file name could not be determined."; - return kUpdateNotAttempted; - } - const std::string& oat_file_name = *info.Filename(); - const std::string& vdex_file_name = GetVdexFilename(oat_file_name); - - // dex2oat ignores missing dex files and doesn't report an error. - // Check explicitly here so we can detect the error properly. - // TODO: Why does dex2oat behave that way? - struct stat dex_path_stat; - if (TEMP_FAILURE_RETRY(stat(dex_location_.c_str(), &dex_path_stat)) != 0) { - *error_msg = "Could not access dex location " + dex_location_ + ":" + strerror(errno); - return kUpdateNotAttempted; - } - - // If this is the odex location, we need to create the odex file layout (../oat/isa/..) - if (!info.IsOatLocation()) { - if (!PrepareOdexDirectories(dex_location_, oat_file_name, isa_, error_msg)) { - return kUpdateNotAttempted; - } - } - - // Set the permissions for the oat and the vdex files. - // The user always gets read and write while the group and others propagate - // the reading access of the original dex file. - mode_t file_mode = S_IRUSR | S_IWUSR | - (dex_path_stat.st_mode & S_IRGRP) | - (dex_path_stat.st_mode & S_IROTH); - - Dex2oatFileWrapper vdex_file_wrapper(OS::CreateEmptyFile(vdex_file_name.c_str())); - File* vdex_file = vdex_file_wrapper.GetFile(); - if (vdex_file == nullptr) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the vdex file " + vdex_file_name - + " could not be opened."; - return kUpdateNotAttempted; - } - - if (fchmod(vdex_file->Fd(), file_mode) != 0) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the vdex file " + vdex_file_name - + " could not be made world readable."; - return kUpdateNotAttempted; - } - - Dex2oatFileWrapper oat_file_wrapper(OS::CreateEmptyFile(oat_file_name.c_str())); - File* oat_file = oat_file_wrapper.GetFile(); - if (oat_file == nullptr) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the oat file could not be created."; - return kUpdateNotAttempted; - } - - if (fchmod(oat_file->Fd(), file_mode) != 0) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the oat file could not be made world readable."; - return kUpdateNotAttempted; - } - - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location_); - args.push_back("--output-vdex-fd=" + std::to_string(vdex_file->Fd())); - args.push_back("--oat-fd=" + std::to_string(oat_file->Fd())); - args.push_back("--oat-location=" + oat_file_name); - args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); - const std::string dex2oat_context = class_loader_context == nullptr - ? OatFile::kSpecialSharedLibrary - : class_loader_context->EncodeContextForDex2oat(/*base_dir*/ ""); - args.push_back("--class-loader-context=" + dex2oat_context); - - if (!Dex2Oat(args, error_msg)) { - return kUpdateFailed; - } - - if (vdex_file->FlushCloseOrErase() != 0) { - *error_msg = "Unable to close vdex file " + vdex_file_name; - return kUpdateFailed; - } - - if (oat_file->FlushCloseOrErase() != 0) { - *error_msg = "Unable to close oat file " + oat_file_name; - return kUpdateFailed; - } - - // Mark that the odex file has changed and we should try to reload. - info.Reset(); - // We have compiled successfully. Disable the auto-unlink. - vdex_file_wrapper.DisableUnlinkAtDestruction(); - oat_file_wrapper.DisableUnlinkAtDestruction(); - - return kUpdateSucceeded; -} - -bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args, - std::string* error_msg) { - Runtime* runtime = Runtime::Current(); - std::string image_location = ImageLocation(); - if (image_location.empty()) { - *error_msg = "No image location found for Dex2Oat."; - return false; - } - - std::vector<std::string> argv; - argv.push_back(runtime->GetCompilerExecutable()); - if (runtime->IsJavaDebuggable()) { - argv.push_back("--debuggable"); - } - runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); - - if (!runtime->IsVerificationEnabled()) { - argv.push_back("--compiler-filter=verify-none"); - } - - if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kNoChecks) { - argv.push_back("--runtime-arg"); - argv.push_back("-Xhidden-api-checks"); - } - - if (runtime->MustRelocateIfPossible()) { - argv.push_back("--runtime-arg"); - argv.push_back("-Xrelocate"); - } else { - argv.push_back("--runtime-arg"); - argv.push_back("-Xnorelocate"); - } - - if (!kIsTargetBuild) { - argv.push_back("--host"); - } - - argv.push_back("--boot-image=" + image_location); - - std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); - argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); - - argv.insert(argv.end(), args.begin(), args.end()); - - std::string command_line(android::base::Join(argv, ' ')); - return Exec(argv, error_msg); -} - bool OatFileAssistant::DexLocationToOdexFilename(const std::string& location, InstructionSet isa, std::string* odex_filename, @@ -885,16 +561,6 @@ bool OatFileAssistant::DexLocationToOatFilename(const std::string& location, return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg); } -std::string OatFileAssistant::ImageLocation() { - Runtime* runtime = Runtime::Current(); - const std::vector<gc::space::ImageSpace*>& image_spaces = - runtime->GetHeap()->GetBootImageSpaces(); - if (image_spaces.empty()) { - return ""; - } - return image_spaces[0]->GetImageLocation(); -} - const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { if (!required_dex_checksums_attempted_) { required_dex_checksums_attempted_ = true; @@ -1165,22 +831,22 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { vdex_fd_, oat_fd_, filename_.c_str(), - nullptr, - nullptr, + /* requested_base */ nullptr, executable, - false /* low_4gb */, + /* low_4gb */ false, oat_file_assistant_->dex_location_.c_str(), + /* reservation */ nullptr, &error_msg)); } } else { file_.reset(OatFile::Open(/* zip_fd */ -1, filename_.c_str(), filename_.c_str(), - nullptr, - nullptr, + /* requested_base */ nullptr, executable, - false /* low_4gb */, + /* low_4gb */ false, oat_file_assistant_->dex_location_.c_str(), + /* reservation */ nullptr, &error_msg)); } if (file_.get() == nullptr) { diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index a6d0961835..dbfbdf9fbc 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -48,11 +48,6 @@ class ImageSpace; // dex location is in the boot class path. class OatFileAssistant { public: - // The default compile filter to use when optimizing dex file at load time if they - // are out of date. - static const CompilerFilter::Filter kDefaultCompilerFilterForDexLoading = - CompilerFilter::kQuicken; - enum DexOptNeeded { // No dexopt should (or can) be done to update the apk/jar. // Matches Java: dalvik.system.DexFile.NO_DEXOPT_NEEDED = 0 @@ -144,24 +139,6 @@ class OatFileAssistant { // path. bool IsInBootClassPath(); - // Obtains a lock on the target oat file. - // Only one OatFileAssistant object can hold the lock for a target oat file - // at a time. The Lock is released automatically when the OatFileAssistant - // object goes out of scope. The Lock() method must not be called if the - // lock has already been acquired. - // - // Returns true on success. - // Returns false on error, in which case error_msg will contain more - // information on the error. - // - // The 'error_msg' argument must not be null. - // - // This is intended to be used to avoid race conditions when multiple - // processes generate oat files, such as when a foreground Activity and - // a background Service both use DexClassLoaders pointing to the same dex - // file. - bool Lock(std::string* error_msg); - // Return what action needs to be taken to produce up-to-date code for this // dex location. If "downgrade" is set to false, it verifies if the current // compiler filter is at least as good as an oat file generated with the @@ -187,33 +164,6 @@ class OatFileAssistant { // irrespective of the compiler filter of the up-to-date code. bool IsUpToDate(); - // Return code used when attempting to generate updated code. - enum ResultOfAttemptToUpdate { - kUpdateFailed, // We tried making the code up to date, but - // encountered an unexpected failure. - kUpdateNotAttempted, // We wanted to update the code, but determined we - // should not make the attempt. - kUpdateSucceeded // We successfully made the code up to date - // (possibly by doing nothing). - }; - - // Attempts to generate or relocate the oat file as needed to make it up to - // date based on the current runtime and compiler options. - // profile_changed should be true to indicate the profile has recently - // changed for this dex location. - // - // If the dex files need to be made up to date, class_loader_context will be - // passed to dex2oat. - // - // Returns the result of attempting to update the code. - // - // If the result is not kUpdateSucceeded, the value of error_msg will be set - // to a string describing why there was a failure or the update was not - // attempted. error_msg must not be null. - ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed, - ClassLoaderContext* class_loader_context, - std::string* error_msg); - // Returns an oat file that can be used for loading dex files. // Returns null if no suitable oat file was found. // @@ -284,18 +234,6 @@ class OatFileAssistant { // Returns the status of the oat file for the dex location. OatStatus OatFileStatus(); - // Executes dex2oat using the current runtime configuration overridden with - // the given arguments. This does not check to see if dex2oat is enabled in - // the runtime configuration. - // Returns true on success. - // - // If there is a failure, the value of error_msg will be set to a string - // describing why there was failure. error_msg must not be null. - // - // TODO: The OatFileAssistant probably isn't the right place to have this - // function. - static bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); - // Constructs the odex file name for the given dex location. // Returns true on success, in which case odex_filename is set to the odex // file name. @@ -436,20 +374,6 @@ class OatFileAssistant { bool file_released_ = false; }; - // Generate the oat file for the given info from the dex file using the - // current runtime compiler options, the specified filter and class loader - // context. - // This does not check the current status before attempting to generate the - // oat file. - // - // If the result is not kUpdateSucceeded, the value of error_msg will be set - // to a string describing why there was a failure or the update was not - // attempted. error_msg must not be null. - ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info, - CompilerFilter::Filter target, - const ClassLoaderContext* class_loader_context, - std::string* error_msg); - // Return info for the best oat file. OatFileInfo& GetBestInfo(); @@ -473,13 +397,6 @@ class OatFileAssistant { // location. OatStatus GivenOatFileStatus(const OatFile& file); - // Returns the current image location. - // Returns an empty string if the image location could not be retrieved. - // - // TODO: This method should belong with an image file manager, not - // the oat file assistant. - static std::string ImageLocation(); - // Gets the dex checksums required for an up-to-date oat file. // Returns cached_required_dex_checksums if the required checksums were // located. Returns null if the required checksums were not found. The diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 0b3c61d474..5a2997809b 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -41,11 +41,6 @@ namespace art { -static const std::string kSpecialSharedLibrary = "&"; // NOLINT [runtime/string] [4] -static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr; - -static constexpr char kDex2oatCmdLineHiddenApiArg[] = " --runtime-arg -Xhidden-api-checks"; - class OatFileAssistantTest : public DexoptTest { public: void VerifyOptimizationStatus(const std::string& file, @@ -68,14 +63,6 @@ class OatFileAssistantTest : public DexoptTest { } }; -class OatFileAssistantNoDex2OatTest : public DexoptTest { - public: - virtual void SetUpRuntimeOptions(RuntimeOptions* options) { - DexoptTest::SetUpRuntimeOptions(options); - options->push_back(std::make_pair("-Xnodex2oat", nullptr)); - } -}; - class ScopedNonWritable { public: explicit ScopedNonWritable(const std::string& dex_location) { @@ -109,6 +96,97 @@ static bool IsExecutedAsRoot() { return geteuid() == 0; } +// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative +// encoded dex locations. +// Expect: The oat file status is kNoDexOptNeeded. +TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) { + std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar"; + std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex"; + + // Create the dex file + Copy(GetMultiDexSrc1(), dex_location); + + // Create the oat file with relative encoded dex location. + std::vector<std::string> args = { + "--dex-file=" + dex_location, + "--dex-location=" + std::string("RelativeEncodedDexLocation.jar"), + "--oat-file=" + odex_location, + "--compiler-filter=speed" + }; + + std::string error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + // Verify we can load both dex files. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + ASSERT_TRUE(oat_file.get() != nullptr); + EXPECT_TRUE(oat_file->IsExecutable()); + std::vector<std::unique_ptr<const DexFile>> dex_files; + dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str()); + EXPECT_EQ(2u, dex_files.size()); +} + +TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + std::string context_str = "PCL[" + context_location + "]"; + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + std::string error_msg; + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + args.push_back("--class-loader-context=" + context_str); + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + EXPECT_NE(nullptr, oat_file.get()); + EXPECT_EQ(context->EncodeContextForOatFile(""), + oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); +} + +TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + std::string context_str = "PCL[" + context_location + "]"; + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + std::string error_msg; + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + args.push_back("--class-loader-context=" + context_str); + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + // A relative context simulates a dependent split context. + std::unique_ptr<ClassLoaderContext> relative_context = + ClassLoaderContext::Create("PCL[ContextDex.jar]"); + EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded( + CompilerFilter::kDefaultCompilerFilter, + /* downgrade */ false, + /* profile_changed */ false, + relative_context.get())); +} + // Case: We have a DEX file, but no OAT file for it. // Expect: The status is kDex2OatNeeded. TEST_F(OatFileAssistantTest, DexNoOat) { @@ -145,11 +223,6 @@ TEST_F(OatFileAssistantTest, NoDexNoOat) { oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - // Trying to make the oat file up to date should not fail or crash. - std::string error_msg; - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - // Trying to get the best oat file should fail, but not crash. std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); EXPECT_EQ(nullptr, oat_file.get()); @@ -584,37 +657,6 @@ TEST_F(OatFileAssistantTest, StrippedMultiDexNonMainOutOfDate) { EXPECT_EQ(OatFileAssistant::kOatDexOutOfDate, oat_file_assistant.OatFileStatus()); } -// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative -// encoded dex locations. -// Expect: The oat file status is kNoDexOptNeeded. -TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) { - std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar"; - std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex"; - - // Create the dex file - Copy(GetMultiDexSrc1(), dex_location); - - // Create the oat file with relative encoded dex location. - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location); - args.push_back("--dex-location=" + std::string("RelativeEncodedDexLocation.jar")); - args.push_back("--oat-file=" + odex_location); - args.push_back("--compiler-filter=speed"); - - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; - - // Verify we can load both dex files. - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - ASSERT_TRUE(oat_file.get() != nullptr); - EXPECT_TRUE(oat_file->IsExecutable()); - std::vector<std::unique_ptr<const DexFile>> dex_files; - dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str()); - EXPECT_EQ(2u, dex_files.size()); -} - // Case: We have a DEX file and an OAT file out of date with respect to the // dex checksum. TEST_F(OatFileAssistantTest, OatDexOutOfDate) { @@ -872,13 +914,6 @@ TEST_F(OatFileAssistantTest, ResourceOnlyDex) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - // Make the oat file up to date. This should have no effect. - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -1037,35 +1072,6 @@ TEST_F(OatFileAssistantTest, LoadNoExecOatUpToDate) { EXPECT_EQ(1u, dex_files.size()); } -// Case: We don't have a DEX file and can't write the oat file. -// Expect: We should fail to generate the oat file without crashing. -TEST_F(OatFileAssistantTest, GenNoDex) { - if (IsExecutedAsRoot()) { - // We cannot simulate non writable locations when executed as root: b/38000545. - LOG(ERROR) << "Test skipped because it's running as root"; - return; - } - - std::string dex_location = GetScratchDir() + "/GenNoDex.jar"; - - ScopedNonWritable scoped_non_writable(dex_location); - ASSERT_TRUE(scoped_non_writable.IsSuccessful()); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - // We should get kUpdateSucceeded from MakeUpToDate since there's nothing - // that can be done in this situation. - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - - // Verify it didn't create an oat in the default location (dalvik-cache). - OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false); - EXPECT_EQ(OatFileAssistant::kOatCannotOpen, ofm.OatFileStatus()); - // Verify it didn't create the odex file in the default location (../oat/isa/...odex) - EXPECT_EQ(OatFileAssistant::kOatCannotOpen, ofm.OdexFileStatus()); -} - // Turn an absolute path into a path relative to the current working // directory. static std::string MakePathRelative(const std::string& target) { @@ -1131,13 +1137,6 @@ TEST_F(OatFileAssistantTest, ShortDexLocation) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus()); EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - - // Trying to make it up to date should have no effect. - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - EXPECT_TRUE(error_msg.empty()); } // Case: Non-standard extension for dex file. @@ -1156,11 +1155,12 @@ TEST_F(OatFileAssistantTest, LongDexExtension) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); } + // A task to generate a dex location. Used by the RaceToGenerate test. class RaceGenerateTask : public Task { public: - explicit RaceGenerateTask(const std::string& dex_location, const std::string& oat_location) - : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr) + RaceGenerateTask(const std::string& dex_location, const std::string& oat_location) + : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr) {} void Run(Thread* self ATTRIBUTE_UNUSED) { @@ -1169,6 +1169,21 @@ class RaceGenerateTask : public Task { std::vector<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> error_msgs; const OatFile* oat_file = nullptr; + { + // Create the oat file. + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location_); + args.push_back("--oat-file=" + oat_location_); + std::string error_msg; + if (kIsTargetBuild) { + // Don't check whether dex2oat is successful: given we're running kNumThreads in + // parallel, low memory killer might just kill some of the dex2oat invocations. + DexoptTest::Dex2Oat(args, &error_msg); + } else { + ASSERT_TRUE(DexoptTest::Dex2Oat(args, &error_msg)) << error_msg; + } + } + dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat( dex_location_.c_str(), Runtime::Current()->GetSystemClassLoader(), @@ -1176,8 +1191,9 @@ class RaceGenerateTask : public Task { &oat_file, &error_msgs); CHECK(!dex_files.empty()) << android::base::Join(error_msgs, '\n'); - CHECK(dex_files[0]->GetOatDexFile() != nullptr) << dex_files[0]->GetLocation(); - loaded_oat_file_ = dex_files[0]->GetOatDexFile()->GetOatFile(); + if (dex_files[0]->GetOatDexFile() != nullptr) { + loaded_oat_file_ = dex_files[0]->GetOatDexFile()->GetOatFile(); + } CHECK_EQ(loaded_oat_file_, oat_file); } @@ -1191,12 +1207,8 @@ class RaceGenerateTask : public Task { const OatFile* loaded_oat_file_; }; -// Test the case where multiple processes race to generate an oat file. -// This simulates multiple processes using multiple threads. -// -// We want unique Oat files to be loaded even when there is a race to load. -// TODO: The test case no longer tests locking the way it was intended since we now get multiple -// copies of the same Oat files mapped at different locations. +// Test the case where dex2oat invocations race with multiple processes trying to +// load the oat file. TEST_F(OatFileAssistantTest, RaceToGenerate) { std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar"; std::string oat_location = GetOdexDir() + "/RaceToGenerate.oat"; @@ -1209,31 +1221,32 @@ TEST_F(OatFileAssistantTest, RaceToGenerate) { // take a while to generate. Copy(GetLibCoreDexFileNames()[0], dex_location); - const int kNumThreads = 32; + const size_t kNumThreads = 32; Thread* self = Thread::Current(); ThreadPool thread_pool("Oat file assistant test thread pool", kNumThreads); std::vector<std::unique_ptr<RaceGenerateTask>> tasks; - for (int i = 0; i < kNumThreads; i++) { + for (size_t i = 0; i < kNumThreads; i++) { std::unique_ptr<RaceGenerateTask> task(new RaceGenerateTask(dex_location, oat_location)); thread_pool.AddTask(self, task.get()); tasks.push_back(std::move(task)); } thread_pool.StartWorkers(self); - thread_pool.Wait(self, true, false); + thread_pool.Wait(self, /* do_work */ true, /* may_hold_locks */ false); - // Verify every task got a unique oat file. + // Verify that tasks which got an oat file got a unique one. std::set<const OatFile*> oat_files; for (auto& task : tasks) { const OatFile* oat_file = task->GetLoadedOatFile(); - EXPECT_TRUE(oat_files.find(oat_file) == oat_files.end()); - oat_files.insert(oat_file); + if (oat_file != nullptr) { + EXPECT_TRUE(oat_files.find(oat_file) == oat_files.end()); + oat_files.insert(oat_file); + } } } -// Case: We have a DEX file and an ODEX file, no OAT file, and dex2oat is -// disabled. +// Case: We have a DEX file and an ODEX file, and no OAT file, // Expect: We should load the odex file non-executable. -TEST_F(OatFileAssistantNoDex2OatTest, LoadDexOdexNoOat) { +TEST_F(DexoptTest, LoadDexOdexNoOat) { std::string dex_location = GetScratchDir() + "/LoadDexOdexNoOat.jar"; std::string odex_location = GetOdexDir() + "/LoadDexOdexNoOat.odex"; @@ -1252,10 +1265,9 @@ TEST_F(OatFileAssistantNoDex2OatTest, LoadDexOdexNoOat) { EXPECT_EQ(1u, dex_files.size()); } -// Case: We have a MultiDEX file and an ODEX file, no OAT file, and dex2oat is -// disabled. +// Case: We have a MultiDEX file and an ODEX file, and no OAT file. // Expect: We should load the odex file non-executable. -TEST_F(OatFileAssistantNoDex2OatTest, LoadMultiDexOdexNoOat) { +TEST_F(DexoptTest, LoadMultiDexOdexNoOat) { std::string dex_location = GetScratchDir() + "/LoadMultiDexOdexNoOat.jar"; std::string odex_location = GetOdexDir() + "/LoadMultiDexOdexNoOat.odex"; @@ -1274,36 +1286,6 @@ TEST_F(OatFileAssistantNoDex2OatTest, LoadMultiDexOdexNoOat) { EXPECT_EQ(2u, dex_files.size()); } -TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { - std::string dex_location = GetScratchDir() + "/RuntimeCompilerFilterOptionUsed.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=quicken"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); - EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); - - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) - << error_msg; - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); - - Runtime::Current()->AddCompilerOption("--compiler-filter=bogus"); - EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); -} - TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) { std::string error_msg; std::string odex_file; @@ -1350,112 +1332,6 @@ TEST_F(OatFileAssistantTest, DexOptStatusValues) { } } -// Verify that when no compiler filter is passed the default one from OatFileAssistant is used. -TEST_F(OatFileAssistantTest, DefaultMakeUpToDateFilter) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter)); - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(default_filter, oat_file->GetCompilerFilter()); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithSpecialSharedLibrary) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter)); - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(kSpecialSharedLibrary, - oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - std::string context_location = GetScratchDir() + "/ContextDex.jar"; - Copy(GetDexSrc1(), dex_location); - Copy(GetDexSrc2(), context_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - std::string context_str = "PCL[" + context_location + "]"; - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); - ASSERT_TRUE(context != nullptr); - ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(context->EncodeContextForOatFile(""), - oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithHiddenApiDisabled) { - hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption( - hiddenapi::EnforcementPolicy::kNoChecks); - - std::string dex_location = GetScratchDir() + "/TestDexHiddenApiDisabled.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - - const char* cmd_line = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); - EXPECT_NE(nullptr, cmd_line); - EXPECT_EQ(nullptr, strstr(cmd_line, kDex2oatCmdLineHiddenApiArg)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithHiddenApiEnabled) { - hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption( - hiddenapi::EnforcementPolicy::kBlacklistOnly); - - std::string dex_location = GetScratchDir() + "/TestDexHiddenApiEnabled.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - - const char* cmd_line = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); - EXPECT_NE(nullptr, cmd_line); - EXPECT_NE(nullptr, strstr(cmd_line, kDex2oatCmdLineHiddenApiArg)); -} - TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { std::string dex_location = GetScratchDir() + "/TestDex.jar"; std::string context_location = GetScratchDir() + "/ContextDex.jar"; @@ -1464,19 +1340,12 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; std::string error_msg; std::string context_str = "PCL[" + context_location + "]"; std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); ASSERT_TRUE(context != nullptr); ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); - // Update the context by overriding the jar file. Copy(GetMultiDexSrc2(), context_location); std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str); @@ -1484,88 +1353,10 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { // DexOptNeeded should advise compilation from scratch. EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, oat_file_assistant.GetDexOptNeeded( - default_filter, false, false, updated_context.get())); -} - -TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - std::string context_location = GetScratchDir() + "/ContextDex.jar"; - Copy(GetDexSrc1(), dex_location); - Copy(GetDexSrc2(), context_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - std::string context_str = "PCL[" + context_location + "]"; - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); - ASSERT_TRUE(context != nullptr); - ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - // A relative context simulates a dependent split context. - std::unique_ptr<ClassLoaderContext> relative_context = - ClassLoaderContext::Create("PCL[ContextDex.jar]"); - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded( - default_filter, false, false, relative_context.get())); -} - -TEST_F(OatFileAssistantTest, SystemOdex) { - std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; - std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; - std::string system_location = GetAndroidRoot() + "/OatUpToDate.jar"; - - std::string error_msg; - - Copy(GetDexSrc1(), dex_location); - EXPECT_FALSE(LocationIsOnSystem(dex_location.c_str())); - - { - OatFileAssistant oat_file_assistant(dex_location.c_str(), - kRuntimeISA, - true, - false); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - { - OatFileAssistant oat_file_assistant(dex_location.c_str(), - kRuntimeISA, - true, - true); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_FALSE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - Copy(GetDexSrc1(), system_location); - EXPECT_TRUE(LocationIsOnSystem(system_location.c_str())); - - { - OatFileAssistant oat_file_assistant(system_location.c_str(), - kRuntimeISA, - true, - false); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - { - OatFileAssistant oat_file_assistant(system_location.c_str(), - kRuntimeISA, - true, - true); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } + CompilerFilter::kDefaultCompilerFilter, + /* downgrade */ false, + /* profile_changed */ false, + updated_context.get())); } // TODO: More Tests: diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 59a1045ba2..bcad4a3428 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -465,57 +465,15 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( !runtime->IsAotCompiler(), only_use_system_oat_files_); - // Lock the target oat location to avoid races generating and loading the - // oat file. - std::string error_msg; - if (!oat_file_assistant.Lock(/*out*/&error_msg)) { - // Don't worry too much if this fails. If it does fail, it's unlikely we - // can generate an oat file anyway. - VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg; - } - - const OatFile* source_oat_file = nullptr; - - if (!oat_file_assistant.IsUpToDate()) { - // Update the oat file on disk if we can, based on the --compiler-filter - // option derived from the current runtime options. - // This may fail, but that's okay. Best effort is all that matters here. - // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile - // secondary dex files in isolation (and avoid to extract/verify the main apk - // if it's in the class path). Note this trades correctness for performance - // since the resulting slow down is unacceptable in some cases until b/64530081 - // is fixed. - // We still pass the class loader context when the classpath string of the runtime - // is not empty, which is the situation when ART is invoked standalone. - ClassLoaderContext* actual_context = Runtime::Current()->GetClassPathString().empty() - ? nullptr - : context.get(); - switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false, - actual_context, - /*out*/ &error_msg)) { - case OatFileAssistant::kUpdateFailed: - LOG(WARNING) << error_msg; - break; - - case OatFileAssistant::kUpdateNotAttempted: - // Avoid spamming the logs if we decided not to attempt making the oat - // file up to date. - VLOG(oat) << error_msg; - break; - - case OatFileAssistant::kUpdateSucceeded: - // Nothing to do. - break; - } - } - // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()=" << reinterpret_cast<uintptr_t>(oat_file.get()) << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")"; + const OatFile* source_oat_file = nullptr; CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions; + std::string error_msg; if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it diff --git a/runtime/oat_file_test.cc b/runtime/oat_file_test.cc index 12dfe20d56..51d8fca6c5 100644 --- a/runtime/oat_file_test.cc +++ b/runtime/oat_file_test.cc @@ -77,11 +77,11 @@ TEST_F(OatFileTest, LoadOat) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file.get() != nullptr); @@ -105,11 +105,11 @@ TEST_F(OatFileTest, ChangingMultiDexUncompressed) { std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); ASSERT_TRUE(odex_file != nullptr); ASSERT_EQ(2u, odex_file->GetOatDexFiles().size()); @@ -120,13 +120,13 @@ TEST_F(OatFileTest, ChangingMultiDexUncompressed) { // And try to load again. std::unique_ptr<OatFile> odex_file(OatFile::Open(/* zip_fd */ -1, - oat_location.c_str(), - oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, + oat_location, + oat_location, + /* requested_base */ nullptr, + /* executable */ false, + /* low_4gb */ false, dex_location.c_str(), + /* reservation */ nullptr, &error_msg)); EXPECT_TRUE(odex_file == nullptr); EXPECT_NE(std::string::npos, error_msg.find("expected 2 uncompressed dex files, but found 1")) diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 0d98d90f1e..4d16eb537d 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -220,9 +220,6 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define({"-Xrelocate", "-Xnorelocate"}) .WithValues({true, false}) .IntoKey(M::Relocate) - .Define({"-Xdex2oat", "-Xnodex2oat"}) - .WithValues({true, false}) - .IntoKey(M::Dex2Oat) .Define({"-Ximage-dex2oat", "-Xnoimage-dex2oat"}) .WithValues({true, false}) .IntoKey(M::ImageDex2Oat) diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 243150759b..6878cc08c8 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -216,7 +216,6 @@ Runtime::Runtime() must_relocate_(false), is_concurrent_gc_enabled_(true), is_explicit_gc_disabled_(false), - dex2oat_enabled_(true), image_dex2oat_enabled_(true), default_stack_size_(0), heap_(nullptr), @@ -1195,7 +1194,6 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { must_relocate_ = runtime_options.GetOrDefault(Opt::Relocate); is_zygote_ = runtime_options.Exists(Opt::Zygote); is_explicit_gc_disabled_ = runtime_options.Exists(Opt::DisableExplicitGC); - dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::Dex2Oat); image_dex2oat_enabled_ = runtime_options.GetOrDefault(Opt::ImageDex2Oat); dump_native_stack_on_sig_quit_ = runtime_options.GetOrDefault(Opt::DumpNativeStackOnSigQuit); diff --git a/runtime/runtime.h b/runtime/runtime.h index f98d7b9829..f0bf7548af 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -142,10 +142,6 @@ class Runtime { return must_relocate_; } - bool IsDex2OatEnabled() const { - return dex2oat_enabled_ && IsImageDex2OatEnabled(); - } - bool IsImageDex2OatEnabled() const { return image_dex2oat_enabled_; } @@ -846,7 +842,6 @@ class Runtime { bool must_relocate_; bool is_concurrent_gc_enabled_; bool is_explicit_gc_disabled_; - bool dex2oat_enabled_; bool image_dex2oat_enabled_; std::string compiler_executable_; diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 5e12bbce05..ae1e08f10b 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -21,7 +21,7 @@ // This file defines the list of keys for RuntimeOptions. // These can be used with RuntimeOptions.Get/Set/etc, for example: -// RuntimeOptions opt; bool* dex2oat_enabled = opt.Get(RuntimeOptions::Dex2Oat); +// RuntimeOptions opt; bool* image_dex2oat_enabled = opt.Get(RuntimeOptions::ImageDex2Oat); // // Column Descriptions: // <<Type>> <<Key Name>> <<Default Value>> @@ -88,7 +88,6 @@ RUNTIME_OPTIONS_KEY (std::vector<std::string>, \ RUNTIME_OPTIONS_KEY (std::string, JniTrace) RUNTIME_OPTIONS_KEY (std::string, PatchOat) RUNTIME_OPTIONS_KEY (bool, Relocate, kDefaultMustRelocate) -RUNTIME_OPTIONS_KEY (bool, Dex2Oat, true) RUNTIME_OPTIONS_KEY (bool, ImageDex2Oat, true) RUNTIME_OPTIONS_KEY (bool, Interpret, false) // -Xint // Disable the compiler for CC (for now). diff --git a/runtime/trace.cc b/runtime/trace.cc index 1986eec353..949fabe7f9 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -383,9 +383,6 @@ void Trace::Start(std::unique_ptr<File>&& trace_file_in, } }; std::unique_ptr<File, decltype(deleter)> trace_file(trace_file_in.release(), deleter); - if (trace_file != nullptr) { - trace_file->DisableAutoClose(); - } Thread* self = Thread::Current(); { @@ -420,7 +417,7 @@ void Trace::Start(std::unique_ptr<File>&& trace_file_in, if (the_trace_ != nullptr) { LOG(ERROR) << "Trace already in progress, ignoring this request"; } else { - enable_stats = (flags && kTraceCountAllocs) != 0; + enable_stats = (flags & kTraceCountAllocs) != 0; the_trace_ = new Trace(trace_file.release(), buffer_size, flags, output_mode, trace_mode); if (trace_mode == TraceMode::kSampling) { CHECK_PTHREAD_CALL(pthread_create, (&sampling_pthread_, nullptr, &RunSamplingThread, @@ -608,7 +605,7 @@ void Trace::Resume() { Runtime* runtime = Runtime::Current(); // Enable count of allocs if specified in the flags. - bool enable_stats = (the_trace->flags_ && kTraceCountAllocs) != 0; + bool enable_stats = (the_trace->flags_ & kTraceCountAllocs) != 0; { gc::ScopedGCCriticalSection gcs(self, diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index a7dc2257f1..452cd8e359 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -150,10 +150,11 @@ std::unique_ptr<VdexFile> VdexFile::OpenAtAddress(uint8_t* mmap_addr, (writable || unquicken) ? PROT_READ | PROT_WRITE : PROT_READ, unquicken ? MAP_PRIVATE : MAP_SHARED, file_fd, - 0 /* start offset */, + /* start */ 0u, low_4gb, - mmap_reuse, vdex_filename.c_str(), + mmap_reuse, + /* reservation */ nullptr, error_msg); if (!mmap.IsValid()) { *error_msg = "Failed to mmap file " + vdex_filename + " : " + *error_msg; diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index a1b8938eaa..e22afaa72a 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -149,6 +149,7 @@ FailureKind MethodVerifier::VerifyClass(Thread* self, CompilerCallbacks* callbacks, bool allow_soft_failures, HardFailLogMode log_level, + uint32_t api_level, std::string* error) { if (klass->IsVerified()) { return FailureKind::kNoFailure; @@ -188,6 +189,7 @@ FailureKind MethodVerifier::VerifyClass(Thread* self, callbacks, allow_soft_failures, log_level, + api_level, error); } @@ -211,6 +213,7 @@ FailureKind MethodVerifier::VerifyClass(Thread* self, CompilerCallbacks* callbacks, bool allow_soft_failures, HardFailLogMode log_level, + uint32_t api_level, std::string* error) { // A class must not be abstract and final. if ((class_def.access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) { @@ -261,6 +264,7 @@ FailureKind MethodVerifier::VerifyClass(Thread* self, allow_soft_failures, log_level, /*need_precise_constants*/ false, + api_level, &hard_failure_msg); if (result.kind == FailureKind::kHardFailure) { if (failure_data.kind == FailureKind::kHardFailure) { @@ -322,6 +326,7 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, + uint32_t api_level, std::string* hard_failure_msg) { MethodVerifier::FailureData result; uint64_t start_ns = kTimeVerifyMethod ? NanoTime() : 0; @@ -339,7 +344,8 @@ MethodVerifier::FailureData MethodVerifier::VerifyMethod(Thread* self, allow_soft_failures, need_precise_constants, false /* verify to dump */, - true /* allow_thread_suspension */); + true /* allow_thread_suspension */, + api_level); if (verifier.Verify()) { // Verification completed, however failures may be pending that didn't cause the verification // to hard fail. @@ -458,7 +464,8 @@ MethodVerifier* MethodVerifier::VerifyMethodAndDump(Thread* self, const DexFile::ClassDef& class_def, const DexFile::CodeItem* code_item, ArtMethod* method, - uint32_t method_access_flags) { + uint32_t method_access_flags, + uint32_t api_level) { MethodVerifier* verifier = new MethodVerifier(self, dex_file, dex_cache, @@ -472,7 +479,8 @@ MethodVerifier* MethodVerifier::VerifyMethodAndDump(Thread* self, true /* allow_soft_failures */, true /* need_precise_constants */, true /* verify_to_dump */, - true /* allow_thread_suspension */); + true /* allow_thread_suspension */, + api_level); verifier->Verify(); verifier->DumpFailures(vios->Stream()); vios->Stream() << verifier->info_messages_.str(); @@ -500,7 +508,8 @@ MethodVerifier::MethodVerifier(Thread* self, bool allow_soft_failures, bool need_precise_constants, bool verify_to_dump, - bool allow_thread_suspension) + bool allow_thread_suspension, + uint32_t api_level) : self_(self), arena_stack_(Runtime::Current()->GetArenaPool()), allocator_(&arena_stack_), @@ -534,7 +543,8 @@ MethodVerifier::MethodVerifier(Thread* self, verify_to_dump_(verify_to_dump), allow_thread_suspension_(allow_thread_suspension), is_constructor_(false), - link_(nullptr) { + link_(nullptr), + api_level_(api_level == 0 ? std::numeric_limits<uint32_t>::max() : api_level) { self->PushVerifier(this); } @@ -546,7 +556,8 @@ MethodVerifier::~MethodVerifier() { void MethodVerifier::FindLocksAtDexPc( ArtMethod* m, uint32_t dex_pc, - std::vector<MethodVerifier::DexLockInfo>* monitor_enter_dex_pcs) { + std::vector<MethodVerifier::DexLockInfo>* monitor_enter_dex_pcs, + uint32_t api_level) { StackHandleScope<2> hs(Thread::Current()); Handle<mirror::DexCache> dex_cache(hs.NewHandle(m->GetDexCache())); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(m->GetClassLoader())); @@ -563,7 +574,8 @@ void MethodVerifier::FindLocksAtDexPc( true /* allow_soft_failures */, false /* need_precise_constants */, false /* verify_to_dump */, - false /* allow_thread_suspension */); + false /* allow_thread_suspension */, + api_level); verifier.interesting_dex_pc_ = dex_pc; verifier.monitor_enter_dex_pcs_ = monitor_enter_dex_pcs; verifier.FindLocksAtDexPc(); @@ -3663,9 +3675,11 @@ const RegType& MethodVerifier::ResolveClass(dex::TypeIndex class_idx) { // the access-checks interpreter. If result is primitive, skip the access check. // // Note: we do this for unresolved classes to trigger re-verification at runtime. - if (C == CheckAccess::kYes && result->IsNonZeroReferenceTypes()) { + if (C == CheckAccess::kYes && + result->IsNonZeroReferenceTypes() && + (api_level_ >= 28u || !result->IsUnresolvedTypes())) { const RegType& referrer = GetDeclaringClass(); - if (!referrer.CanAccess(*result)) { + if ((api_level_ >= 28u || !referrer.IsUnresolvedTypes()) && !referrer.CanAccess(*result)) { Fail(VERIFY_ERROR_ACCESS_CLASS) << "(possibly) illegal class access: '" << referrer << "' -> '" << *result << "'"; } @@ -4725,7 +4739,7 @@ void MethodVerifier::VerifyISFieldAccess(const Instruction* inst, const RegType& DCHECK(!can_load_classes_ || self_->IsExceptionPending()); self_->ClearException(); } - } else { + } else if (api_level_ >= 28u) { // If we don't have the field (it seems we failed resolution) and this is a PUT, we need to // redo verification at runtime as the field may be final, unless the field id shows it's in // the same class. diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h index 9890af9d95..eef22807ec 100644 --- a/runtime/verifier/method_verifier.h +++ b/runtime/verifier/method_verifier.h @@ -100,6 +100,7 @@ class MethodVerifier { CompilerCallbacks* callbacks, bool allow_soft_failures, HardFailLogMode log_level, + uint32_t api_level, std::string* error) REQUIRES_SHARED(Locks::mutator_lock_); static FailureKind VerifyClass(Thread* self, @@ -110,6 +111,7 @@ class MethodVerifier { CompilerCallbacks* callbacks, bool allow_soft_failures, HardFailLogMode log_level, + uint32_t api_level, std::string* error) REQUIRES_SHARED(Locks::mutator_lock_); @@ -121,7 +123,8 @@ class MethodVerifier { Handle<mirror::ClassLoader> class_loader, const DexFile::ClassDef& class_def, const DexFile::CodeItem* code_item, ArtMethod* method, - uint32_t method_access_flags) + uint32_t method_access_flags, + uint32_t api_level) REQUIRES_SHARED(Locks::mutator_lock_); uint8_t EncodePcToReferenceMapData() const; @@ -163,8 +166,10 @@ class MethodVerifier { // Fills 'monitor_enter_dex_pcs' with the dex pcs of the monitor-enter instructions corresponding // to the locks held at 'dex_pc' in method 'm'. // Note: this is the only situation where the verifier will visit quickened instructions. - static void FindLocksAtDexPc(ArtMethod* m, uint32_t dex_pc, - std::vector<DexLockInfo>* monitor_enter_dex_pcs) + static void FindLocksAtDexPc(ArtMethod* m, + uint32_t dex_pc, + std::vector<DexLockInfo>* monitor_enter_dex_pcs, + uint32_t api_level) REQUIRES_SHARED(Locks::mutator_lock_); static void Init() REQUIRES_SHARED(Locks::mutator_lock_); @@ -242,7 +247,8 @@ class MethodVerifier { bool allow_soft_failures, bool need_precise_constants, bool verify_to_dump, - bool allow_thread_suspension) + bool allow_thread_suspension, + uint32_t api_level) REQUIRES_SHARED(Locks::mutator_lock_); void UninstantiableError(const char* descriptor); @@ -299,6 +305,7 @@ class MethodVerifier { bool allow_soft_failures, HardFailLogMode log_level, bool need_precise_constants, + uint32_t api_level, std::string* hard_failure_msg) REQUIRES_SHARED(Locks::mutator_lock_); @@ -790,6 +797,10 @@ class MethodVerifier { // Link, for the method verifier root linked list. MethodVerifier* link_; + // API level, for dependent checks. Note: we do not use '0' for unset here, to simplify checks. + // Instead, unset level should correspond to max(). + const uint32_t api_level_; + friend class art::Thread; friend class VerifierDepsTest; diff --git a/runtime/verifier/method_verifier_test.cc b/runtime/verifier/method_verifier_test.cc index d1be9fa6f8..cedc583986 100644 --- a/runtime/verifier/method_verifier_test.cc +++ b/runtime/verifier/method_verifier_test.cc @@ -42,7 +42,7 @@ class MethodVerifierTest : public CommonRuntimeTest { // Verify the class std::string error_msg; FailureKind failure = MethodVerifier::VerifyClass( - self, klass, nullptr, true, HardFailLogMode::kLogWarning, &error_msg); + self, klass, nullptr, true, HardFailLogMode::kLogWarning, /* api_level */ 0u, &error_msg); if (android::base::StartsWith(descriptor, "Ljava/lang/invoke")) { ASSERT_TRUE(failure == FailureKind::kSoftFailure || diff --git a/test/071-dexfile-map-clean/run b/test/071-dexfile-map-clean/run index 9c100ec497..afa2ff7462 100755 --- a/test/071-dexfile-map-clean/run +++ b/test/071-dexfile-map-clean/run @@ -14,13 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Run without dex2oat so that we don't create oat/vdex files -# when trying to load the secondary dex file. - -# In this way, the secondary dex file will be forced to be -# loaded directly. -# -# In addition, make sure we call 'sync' +# Make sure we call 'sync' # before executing dalvikvm because otherwise # it's highly likely the pushed JAR files haven't # been committed to permanent storage yet, @@ -28,4 +22,4 @@ # the memory is dirty (despite being file-backed). # (Note: this was reproducible 100% of the time on # a target angler device). -./default-run "$@" --no-dex2oat --sync +./default-run "$@" --sync diff --git a/test/071-dexfile-map-clean/src/Main.java b/test/071-dexfile-map-clean/src/Main.java index 8a196dd52e..e784ac601c 100644 --- a/test/071-dexfile-map-clean/src/Main.java +++ b/test/071-dexfile-map-clean/src/Main.java @@ -90,19 +90,6 @@ public class Main { return true; } - // This test takes relies on dex2oat being skipped. - // (enforced in 'run' file by using '--no-dex2oat' - // - // This could happen in a non-test situation - // if a secondary dex file is loaded (but not yet maintenance-mode compiled) - // with JIT. - // - // Or it could also happen if a secondary dex file is loaded and forced - // into running into the interpreter (e.g. duplicate classes). - // - // Rather than relying on those weird fallbacks, - // we force the runtime not to dex2oat the dex file to ensure - // this test is repeatable and less brittle. private static void testDexMemoryMaps() throws Exception { // Ensure that the secondary dex file is mapped clean (directly from JAR file). String smaps = new String(Files.readAllBytes(Paths.get("/proc/self/smaps"))); diff --git a/test/116-nodex2oat/expected.txt b/test/116-nodex2oat/expected.txt index 157dfc4ea4..c6c7daa21d 100644 --- a/test/116-nodex2oat/expected.txt +++ b/test/116-nodex2oat/expected.txt @@ -1,9 +1,2 @@ -Run -Xnodex2oat JNI_OnLoad called -Has oat is false, is dex2oat enabled is false. -Run -Xdex2oat -JNI_OnLoad called -Has oat is true, is dex2oat enabled is true. -Run default -JNI_OnLoad called -Has oat is true, is dex2oat enabled is true. +Has oat is false. diff --git a/test/116-nodex2oat/run b/test/116-nodex2oat/run index d7984cece2..9063685fe7 100755 --- a/test/116-nodex2oat/run +++ b/test/116-nodex2oat/run @@ -24,20 +24,4 @@ if [[ "${flags}" == *--prebuild* || "${flags}" != *--no-prebuild* ]] ; then exit 1 fi -# Make sure we can run without an oat file. -echo "Run -Xnodex2oat" -${RUN} ${flags} --runtime-option -Xnodex2oat -return_status1=$? - -# Make sure we can run with the oat file. -echo "Run -Xdex2oat" -${RUN} ${flags} --runtime-option -Xdex2oat -return_status2=$? - -# Make sure we can run with the default settings. -echo "Run default" ${RUN} ${flags} -return_status3=$? - -# Make sure we don't silently ignore an early failure. -(exit $return_status1) && (exit $return_status2) && (exit $return_status3) diff --git a/test/116-nodex2oat/src/Main.java b/test/116-nodex2oat/src/Main.java index 229735f4b8..5491c492a9 100644 --- a/test/116-nodex2oat/src/Main.java +++ b/test/116-nodex2oat/src/Main.java @@ -17,17 +17,8 @@ public class Main { public static void main(String[] args) { System.loadLibrary(args[0]); - System.out.println( - "Has oat is " + hasOatFile() + ", is dex2oat enabled is " + isDex2OatEnabled() + "."); - - if (hasOatFile() && !isDex2OatEnabled()) { - throw new Error("Application with dex2oat disabled runs with an oat file"); - } else if (!hasOatFile() && isDex2OatEnabled()) { - throw new Error("Application with dex2oat enabled runs without an oat file"); - } + System.out.println("Has oat is " + hasOatFile() + "."); } private native static boolean hasOatFile(); - - private native static boolean isDex2OatEnabled(); } diff --git a/test/117-nopatchoat/expected.txt b/test/117-nopatchoat/expected.txt index 0cd4715d09..7a24e31775 100644 --- a/test/117-nopatchoat/expected.txt +++ b/test/117-nopatchoat/expected.txt @@ -1,12 +1,3 @@ -Run without dex2oat/patchoat JNI_OnLoad called -dex2oat & patchoat are disabled, has oat is true, has executable oat is expected. -This is a function call -Run with dexoat/patchoat -JNI_OnLoad called -dex2oat & patchoat are enabled, has oat is true, has executable oat is expected. -This is a function call -Run default -JNI_OnLoad called -dex2oat & patchoat are enabled, has oat is true, has executable oat is expected. +Has oat is true, has executable oat is expected. This is a function call diff --git a/test/117-nopatchoat/run b/test/117-nopatchoat/run index 0627fe5069..4c33f7a450 100755 --- a/test/117-nopatchoat/run +++ b/test/117-nopatchoat/run @@ -34,20 +34,4 @@ if [[ "${flags}" == *--no-relocate* ]] ; then exit 1 fi -# Make sure we can run without relocation -echo "Run without dex2oat/patchoat" -${RUN} ${flags} --runtime-option -Xnodex2oat -return_status1=$? - -# Make sure we can run with the oat file. -echo "Run with dexoat/patchoat" -${RUN} ${flags} --runtime-option -Xdex2oat -return_status2=$? - -# Make sure we can run with the default settings. -echo "Run default" ${RUN} ${flags} -return_status3=$? - -# Make sure we don't silently ignore an early failure. -(exit $return_status1) && (exit $return_status2) && (exit $return_status3) diff --git a/test/117-nopatchoat/src/Main.java b/test/117-nopatchoat/src/Main.java index 816eb171a4..ef47ab9ee4 100644 --- a/test/117-nopatchoat/src/Main.java +++ b/test/117-nopatchoat/src/Main.java @@ -23,18 +23,13 @@ public class Main { // Hitting this condition should be rare and ideally we would prevent it from happening but // there is no way to do so without major changes to the run-test framework. boolean executable_correct = (needsRelocation() ? - hasExecutableOat() == (isDex2OatEnabled() || isRelocationDeltaZero()) : + hasExecutableOat() == isRelocationDeltaZero() : hasExecutableOat() == true); System.out.println( - "dex2oat & patchoat are " + ((isDex2OatEnabled()) ? "enabled" : "disabled") + - ", has oat is " + hasOatFile() + ", has executable oat is " + ( + "Has oat is " + hasOatFile() + ", has executable oat is " + ( executable_correct ? "expected" : "not expected") + "."); - if (!hasOatFile() && isDex2OatEnabled()) { - throw new Error("Application with dex2oat enabled runs without an oat file"); - } - System.out.println(functionCall()); } @@ -47,8 +42,6 @@ public class Main { return ret.substring(0, ret.length() - 1); } - private native static boolean isDex2OatEnabled(); - private native static boolean needsRelocation(); private native static boolean hasOatFile(); diff --git a/test/118-noimage-dex2oat/run b/test/118-noimage-dex2oat/run index e1e2577ae3..d68b0a0b2c 100644 --- a/test/118-noimage-dex2oat/run +++ b/test/118-noimage-dex2oat/run @@ -47,12 +47,12 @@ bpath_arg="--runtime-option -Xbootclasspath:${bpath}" # Make sure we can run without an oat file. echo "Run -Xnoimage-dex2oat" -${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat +${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat return_status1=$? # Make sure we cannot run without an oat file without fallback. echo "Run -Xnoimage-dex2oat -Xno-dex-file-fallback" -${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat --runtime-option -Xnodex2oat \ +${RUN} ${flags} ${bpath_arg} --runtime-option -Xnoimage-dex2oat \ --runtime-option -Xno-dex-file-fallback return_status2=$? diff --git a/test/134-nodex2oat-nofallback/run b/test/134-nodex2oat-nofallback/run index 33265ac471..359d22dfa0 100755 --- a/test/134-nodex2oat-nofallback/run +++ b/test/134-nodex2oat-nofallback/run @@ -17,6 +17,6 @@ flags="${@}" # Make sure we cannot run without an oat file without fallback. -${RUN} ${flags} --runtime-option -Xnodex2oat --runtime-option -Xno-dex-file-fallback +${RUN} ${flags} --runtime-option -Xno-dex-file-fallback # Suppress the exit value. This isn't expected to be successful. echo "Exit status:" $? diff --git a/test/134-nodex2oat-nofallback/src/Main.java b/test/134-nodex2oat-nofallback/src/Main.java index 086ffb9295..73f67c4e38 100644 --- a/test/134-nodex2oat-nofallback/src/Main.java +++ b/test/134-nodex2oat-nofallback/src/Main.java @@ -17,17 +17,8 @@ public class Main { public static void main(String[] args) { System.loadLibrary(args[0]); - System.out.println( - "Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + "."); - - if (hasOat() && !isDex2OatEnabled()) { - throw new Error("Application with dex2oat disabled runs with an oat file"); - } else if (!hasOat() && isDex2OatEnabled()) { - throw new Error("Application with dex2oat enabled runs without an oat file"); - } + System.out.println("Has oat is " + hasOat()); } private native static boolean hasOat(); - - private native static boolean isDex2OatEnabled(); } diff --git a/test/138-duplicate-classes-check2/run b/test/138-duplicate-classes-check2/run deleted file mode 100755 index 8494ad9aad..0000000000 --- a/test/138-duplicate-classes-check2/run +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 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. - -# We want to run as no-dex-file-fallback to confirm that even though the -ex file has a symbolic -# reference to A, there's no class-def, so we don't detect a collision. -exec ${RUN} --runtime-option -Xno-dex-file-fallback "${@}" diff --git a/test/147-stripped-dex-fallback/run b/test/147-stripped-dex-fallback/run index 37c3e1fd88..1f1d22e64e 100755 --- a/test/147-stripped-dex-fallback/run +++ b/test/147-stripped-dex-fallback/run @@ -21,4 +21,4 @@ if [[ "${flags}" == *--no-prebuild* ]] ; then exit 1 fi -${RUN} ${flags} --strip-dex --runtime-option -Xnodex2oat +${RUN} ${flags} --strip-dex diff --git a/test/305-other-fault-handler/fault_handler.cc b/test/305-other-fault-handler/fault_handler.cc index 93bb148745..db3f1f4bf7 100644 --- a/test/305-other-fault-handler/fault_handler.cc +++ b/test/305-other-fault-handler/fault_handler.cc @@ -40,6 +40,7 @@ class TestFaultHandler final : public FaultHandler { /* prot */ PROT_NONE, /* low_4gb */ false, /* reuse */ false, + /* reservation */ nullptr, /* error_msg */ &map_error_, /* use_ashmem */ false)), was_hit_(false) { diff --git a/test/510-checker-try-catch/smali/Runtime.smali b/test/510-checker-try-catch/smali/Runtime.smali index 19b43a3f30..d080a0c94b 100644 --- a/test/510-checker-try-catch/smali/Runtime.smali +++ b/test/510-checker-try-catch/smali/Runtime.smali @@ -549,6 +549,76 @@ .end array-data .end method + +## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after) +## CHECK-DAG: <<Const1:i\d+>> IntConstant 1 +## CHECK-DAG: <<Offset:i\d+>> IntConstant 12 +## CHECK-DAG: <<IndexParam:i\d+>> ParameterValue +## CHECK-DAG: <<Array:l\d+>> ParameterValue + +## CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<Array>>] +## CHECK-DAG: <<Length:i\d+>> ArrayLength +## CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<Length>>] +## CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] +## CHECK-DAG: ArrayGet [<<IntAddr1>>,<<BoundsCh1>>] +## CHECK-DAG: TryBoundary + +## CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>] +## CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<Length>>] +## CHECK-DAG: ArrayGet [<<IntAddr1>>,<<BoundsCh2>>] +## CHECK-DAG: TryBoundary + +## CHECK-DAG: <<Phi:i\d+>> Phi [<<Xplus1>>] +## CHECK-DAG: <<Phiplus1:i\d+>> Add [<<Phi>>,<<Const1>>] +## CHECK-DAG: <<BoundsCh3:i\d+>> BoundsCheck [<<Phiplus1>>,<<Length>>] +## CHECK-DAG: <<IntAddr3:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] +## CHECK-DAG: ArrayGet [<<IntAddr3>>,<<BoundsCh3>>] + +## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after) +## CHECK: NullCheck +## CHECK-NOT: NullCheck + +## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after) +## CHECK: IntermediateAddress +## CHECK: IntermediateAddress +## CHECK-NOT: IntermediateAddress + +## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after) +## CHECK: BoundsCheck +## CHECK: BoundsCheck +## CHECK: BoundsCheck +## CHECK-NOT: BoundsCheck + +## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after) +## CHECK: ArrayGet +## CHECK: ArrayGet +## CHECK: ArrayGet +## CHECK-NOT: ArrayGet +.method public static testIntAddressCatch(I[I)I + .registers 4 + aget v0, p1, p0 + add-int v1, v0, v0 + + :try_start + const/4 v0, 0x1 + add-int p0, p0, v0 + aget v0, p1, p0 + + :try_end + .catch Ljava/lang/ArithmException; {:try_start .. :try_end} :catch_block + + :return + add-int v1, v1, v0 + return v1 + + :catch_block + const/4 v0, 0x1 + add-int p0, p0, v0 + aget v0, p1, p0 + + goto :return +.end method + .field public static intArray:[I .field public static longArray:[J .field public static floatArray:[F diff --git a/test/510-checker-try-catch/src/Main.java b/test/510-checker-try-catch/src/Main.java index d6dcd30f3c..18658cd5e0 100644 --- a/test/510-checker-try-catch/src/Main.java +++ b/test/510-checker-try-catch/src/Main.java @@ -37,6 +37,114 @@ public class Main { public int expected; } + // Test that IntermediateAddress instruction is not alive across BoundsCheck which can throw to + // a catch block. + // + /// CHECK-START-{ARM,ARM64}: void Main.boundsCheckAndCatch(int, int[], int[]) GVN$after_arch (before) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Offset:i\d+>> IntConstant 12 + /// CHECK-DAG: <<IndexParam:i\d+>> ParameterValue + /// CHECK-DAG: <<ArrayA:l\d+>> ParameterValue + /// CHECK-DAG: <<ArrayB:l\d+>> ParameterValue + // + + /// CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<ArrayA>>] + /// CHECK-DAG: <<LengthA:i\d+>> ArrayLength + /// CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<LengthA>>] + /// CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const1>>] + /// CHECK-DAG: TryBoundary + // + /// CHECK-DAG: <<IntAddr2:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr2>>,<<BoundsCh1>>,<<Const2>>] + /// CHECK-DAG: <<NullChB:l\d+>> NullCheck [<<ArrayB>>] + /// CHECK-DAG: <<LengthB:i\d+>> ArrayLength + /// CHECK-DAG: <<BoundsChB:i\d+>> BoundsCheck [<<Const0>>,<<LengthB>>] + /// CHECK-DAG: <<GetB:i\d+>> ArrayGet [<<NullChB>>,<<BoundsChB>>] + /// CHECK-DAG: <<ZeroCheck:i\d+>> DivZeroCheck [<<IndexParam>>] + /// CHECK-DAG: <<Div:i\d+>> Div [<<GetB>>,<<ZeroCheck>>] + /// CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>] + /// CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<LengthA>>] + /// CHECK-DAG: <<IntAddr3:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr3>>,<<BoundsCh2>>,<<Div>>] + /// CHECK-DAG: TryBoundary + // + /// CHECK-DAG: ClearException + /// CHECK-DAG: <<IntAddr4:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr4>>,<<BoundsCh1>>,<<Const1>>] + // + /// CHECK-NOT: NullCheck + /// CHECK-NOT: IntermediateAddress + + /// CHECK-START-{ARM,ARM64}: void Main.boundsCheckAndCatch(int, int[], int[]) GVN$after_arch (after) + /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 + /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2 + /// CHECK-DAG: <<Offset:i\d+>> IntConstant 12 + /// CHECK-DAG: <<IndexParam:i\d+>> ParameterValue + /// CHECK-DAG: <<ArrayA:l\d+>> ParameterValue + /// CHECK-DAG: <<ArrayB:l\d+>> ParameterValue + // + /// CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<ArrayA>>] + /// CHECK-DAG: <<LengthA:i\d+>> ArrayLength + /// CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<LengthA>>] + /// CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const1>>] + /// CHECK-DAG: TryBoundary + // + /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const2>>] + /// CHECK-DAG: <<NullChB:l\d+>> NullCheck [<<ArrayB>>] + /// CHECK-DAG: <<LengthB:i\d+>> ArrayLength + /// CHECK-DAG: <<BoundsChB:i\d+>> BoundsCheck [<<Const0>>,<<LengthB>>] + /// CHECK-DAG: <<GetB:i\d+>> ArrayGet [<<NullChB>>,<<BoundsChB>>] + /// CHECK-DAG: <<ZeroCheck:i\d+>> DivZeroCheck [<<IndexParam>>] + /// CHECK-DAG: <<Div:i\d+>> Div [<<GetB>>,<<ZeroCheck>>] + /// CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>] + /// CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<LengthA>>] + /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh2>>,<<Div>>] + /// CHECK-DAG: TryBoundary + // + /// CHECK-DAG: ClearException + /// CHECK-DAG: <<IntAddr4:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>] + /// CHECK-DAG: ArraySet [<<IntAddr4>>,<<BoundsCh1>>,<<Const1>>] + // + /// CHECK-NOT: NullCheck + /// CHECK-NOT: IntermediateAddress + + // Make sure that BoundsCheck, DivZeroCheck and NullCheck don't stop IntermediateAddress sharing. + public static void boundsCheckAndCatch(int x, int[] a, int[] b) { + a[x] = 1; + try { + a[x] = 2; + a[x + 1] = b[0] / x; + } catch (Exception e) { + a[x] = 1; + } + } + + private static void expectEquals(int expected, int result) { + if (expected != result) { + throw new Error("Expected: " + expected + ", found: " + result); + } + } + + public final static int ARRAY_SIZE = 128; + + public static void testBoundsCheckAndCatch() { + int[] a = new int[ARRAY_SIZE]; + int[] b = new int[ARRAY_SIZE]; + + int index = ARRAY_SIZE - 2; + boundsCheckAndCatch(index, a, b); + expectEquals(2, a[index]); + + index = ARRAY_SIZE - 1; + boundsCheckAndCatch(index, a, b); + expectEquals(1, a[index]); + } + public static void testMethod(String method) throws Exception { Class<?> c = Class.forName("Runtime"); Method m = c.getMethod(method, boolean.class, boolean.class); @@ -52,6 +160,14 @@ public class Main { } } + public static void testIntAddressCatch() throws Exception { + int[] a = new int[3]; + + Class<?> c = Class.forName("Runtime"); + Method m = c.getMethod("testIntAddressCatch", int.class, Class.forName("[I")); + m.invoke(null, 0, a); + } + public static void main(String[] args) throws Exception { testMethod("testUseAfterCatch_int"); testMethod("testUseAfterCatch_long"); @@ -64,5 +180,8 @@ public class Main { testMethod("testCatchPhi_double"); testMethod("testCatchPhi_singleSlot"); testMethod("testCatchPhi_doubleSlot"); + + testBoundsCheckAndCatch(); + testIntAddressCatch(); } } diff --git a/test/527-checker-array-access-split/src/Main.java b/test/527-checker-array-access-split/src/Main.java index 935b37858d..f83c924de9 100644 --- a/test/527-checker-array-access-split/src/Main.java +++ b/test/527-checker-array-access-split/src/Main.java @@ -552,6 +552,28 @@ public class Main { return (int)s; } + // + // Check that IntermediateAddress can be shared across BoundsCheck, DivZeroCheck and NullCheck - + // instruction which have fatal slow paths. + // + /// CHECK-START-{ARM,ARM64}: void Main.checkGVNForFatalChecks(int, int, char[], int[]) GVN$after_arch (before) + /// CHECK: IntermediateAddress + /// CHECK: IntermediateAddress + // + /// CHECK-NOT: IntermediateAddress + + /// CHECK-START-{ARM,ARM64}: void Main.checkGVNForFatalChecks(int, int, char[], int[]) GVN$after_arch (after) + /// CHECK: IntermediateAddress + // + /// CHECK-NOT: IntermediateAddress + public final static void checkGVNForFatalChecks(int begin, int end, char[] buf1, int[] buf2) { + buf1[begin] = 'a'; + buf2[0] = begin / end; + buf1[end] = 'n'; + } + + public final static int ARRAY_SIZE = 128; + public static void main(String[] args) { int[] array = {123, 456, 789}; @@ -575,5 +597,10 @@ public class Main { assertIntEquals(2097152, canMergeAfterBCE2()); assertIntEquals(18, checkLongFloatDouble()); + + char[] c1 = new char[ARRAY_SIZE]; + int[] i1 = new int[ARRAY_SIZE]; + checkGVNForFatalChecks(1, 2, c1, i1); + assertIntEquals('n', c1[2]); } } diff --git a/test/667-jit-jni-stub/run b/test/667-jit-jni-stub/run index f235c6bc90..b7ce9132ab 100755 --- a/test/667-jit-jni-stub/run +++ b/test/667-jit-jni-stub/run @@ -16,4 +16,4 @@ # Disable AOT compilation of JNI stubs. # Ensure this test is not subject to unexpected code collection. -${RUN} "${@}" --no-prebuild --no-dex2oat --runtime-option -Xjitinitialsize:32M +${RUN} "${@}" --no-prebuild --runtime-option -Xjitinitialsize:32M diff --git a/test/667-jit-jni-stub/src/Main.java b/test/667-jit-jni-stub/src/Main.java index 794308d6e1..234c5daf4c 100644 --- a/test/667-jit-jni-stub/src/Main.java +++ b/test/667-jit-jni-stub/src/Main.java @@ -18,7 +18,7 @@ public class Main { public static void main(String[] args) throws Exception { System.loadLibrary(args[0]); if (isAotCompiled(Main.class, "hasJit")) { - throw new Error("This test must be run with --no-prebuild --no-dex2oat!"); + throw new Error("This test must be run with --no-prebuild!"); } if (!hasJit()) { return; diff --git a/test/677-fsi/expected.txt b/test/677-fsi/expected.txt index c7fb8fed77..2b073430b6 100644 --- a/test/677-fsi/expected.txt +++ b/test/677-fsi/expected.txt @@ -1,3 +1,2 @@ oat file has dex code, but APK has uncompressed dex code -oat file has dex code, but APK has uncompressed dex code Hello World diff --git a/test/677-fsi2/expected.txt b/test/677-fsi2/expected.txt index de008470fe..557db03de9 100644 --- a/test/677-fsi2/expected.txt +++ b/test/677-fsi2/expected.txt @@ -1,4 +1 @@ -Run default -Hello World -Run without dex2oat Hello World diff --git a/test/677-fsi2/run b/test/677-fsi2/run index 039a6a78f0..651f082863 100644 --- a/test/677-fsi2/run +++ b/test/677-fsi2/run @@ -14,12 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -echo "Run default" ${RUN} $@ --runtime-option -Xonly-use-system-oat-files -return_status1=$? - -echo "Run without dex2oat" -${RUN} $@ --no-dex2oat --runtime-option -Xonly-use-system-oat-files -return_status2=$? - -(exit $return_status1) && (exit $return_status2) diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java index 9f4caecccd..af18193388 100644 --- a/test/706-checker-scheduler/src/Main.java +++ b/test/706-checker-scheduler/src/Main.java @@ -292,8 +292,7 @@ public class Main { /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: Add loop:<<Loop>> outer_loop:none - /// CHECK: <<Addr2:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none - /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr2>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: Add loop:<<Loop>> outer_loop:none /// CHECK-START-ARM64: void Main.CrossOverLoop(int[], int[]) scheduler (after) @@ -303,13 +302,12 @@ public class Main { /// CHECK: <<NullA:l\d+>> NullCheck [<<ParamA>>] loop:none /// CHECK: Phi loop:<<Loop:B\d+>> outer_loop:none /// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none - /// CHECK: Add loop:<<Loop>> outer_loop:none /// CHECK: <<Addr1:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none + /// CHECK: Add [<<ArrayGet1>>,{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: Add loop:<<Loop>> outer_loop:none - /// CHECK: <<Addr2:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none - /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr2>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none + /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none /// CHECK: Add loop:<<Loop>> outer_loop:none private static void CrossOverLoop(int a[], int b[]) { b[20] = 99; diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc index 2a06a7b9d6..521f9a6c72 100644 --- a/test/906-iterate-heap/iterate_heap.cc +++ b/test/906-iterate-heap/iterate_heap.cc @@ -418,5 +418,21 @@ extern "C" JNIEXPORT jboolean JNICALL Java_art_Test906_checkInitialized( return (status & JVMTI_CLASS_STATUS_INITIALIZED) != 0; } +extern "C" JNIEXPORT jint JNICALL Java_art_Test906_iterateOverInstancesCount( + JNIEnv* env, jclass, jclass target) { + jint cnt = 0; + auto count_func = [](jlong, jlong, jlong*, void* user_data) -> jvmtiIterationControl { + *reinterpret_cast<jint*>(user_data) += 1; + return JVMTI_ITERATION_CONTINUE; + }; + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->IterateOverInstancesOfClass(target, + JVMTI_HEAP_OBJECT_EITHER, + count_func, + &cnt)); + return cnt; +} + } // namespace Test906IterateHeap } // namespace art diff --git a/test/906-iterate-heap/src/art/Test906.java b/test/906-iterate-heap/src/art/Test906.java index be9663a6d4..190f36f5d4 100644 --- a/test/906-iterate-heap/src/art/Test906.java +++ b/test/906-iterate-heap/src/art/Test906.java @@ -18,6 +18,7 @@ package art; import java.util.ArrayList; import java.util.Collections; +import java.util.Random; public class Test906 { public static void run() throws Exception { @@ -69,6 +70,40 @@ public class Test906 { throw lastThrow; } + private static Object[] GenTs(Class<?> k) throws Exception { + Object[] ret = new Object[new Random().nextInt(100) + 10]; + for (int i = 0; i < ret.length; i++) { + ret[i] = k.newInstance(); + } + return ret; + } + + private static void checkEq(int a, int b) { + if (a != b) { + Error e = new Error("Failed: Expected equal " + a + " and " + b); + System.out.println(e); + e.printStackTrace(System.out); + } + } + + public static class Foo {} + public static class Bar extends Foo {} + public static class Baz extends Bar {} + public static class Alpha extends Bar {} + public static class MISSING extends Baz {} + 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); + 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)); + } + public static void doTest() throws Exception { A a = new A(); B b = new B(); @@ -86,6 +121,8 @@ public class Test906 { testHeapCount(); + testIterateOverInstances(); + long classTags[] = new long[100]; long sizes[] = new long[100]; long tags[] = new long[100]; @@ -308,6 +345,8 @@ public class Test906 { return Main.getTag(o); } + private static native int iterateOverInstancesCount(Class<?> klass); + private static native boolean checkInitialized(Class<?> klass); private static native int iterateThroughHeapCount(int heapFilter, Class<?> klassFilter, int stopAfter); diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index 9344b24b5d..da79164f12 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -71,13 +71,6 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_runtimeIsSoftFail(JNIEnv* env AT return Runtime::Current()->IsVerificationSoftFail() ? JNI_TRUE : JNI_FALSE; } -// public static native boolean isDex2OatEnabled(); - -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isDex2OatEnabled(JNIEnv* env ATTRIBUTE_UNUSED, - jclass cls ATTRIBUTE_UNUSED) { - return Runtime::Current()->IsDex2OatEnabled(); -} - // public static native boolean hasImage(); extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasImage(JNIEnv* env ATTRIBUTE_UNUSED, diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 6555b46c0d..bd58ae37ec 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -59,7 +59,7 @@ USE_JVMTI="n" VERIFY="y" # y=yes,n=no,s=softfail ZYGOTE="" DEX_VERIFY="" -USE_DEX2OAT_AND_PATCHOAT="y" +USE_PATCHOAT="y" INSTRUCTION_SET_FEATURES="" ARGS="" EXTERNAL_LOG_TAGS="n" # if y respect externally set ANDROID_LOG_TAGS. @@ -166,14 +166,9 @@ while true; do shift BOOT_IMAGE="$1" shift - elif [ "x$1" = "x--no-dex2oat" ]; then - DEX2OAT="-Xcompiler:${FALSE_BIN}" - USE_DEX2OAT_AND_PATCHOAT="n" - PREBUILD="n" # Do not use prebuilt odex, either. - shift elif [ "x$1" = "x--no-patchoat" ]; then PATCHOAT="-Xpatchoat:${FALSE_BIN}" - USE_DEX2OAT_AND_PATCHOAT="n" + USE_PATCHOAT="n" shift elif [ "x$1" = "x--relocate" ]; then RELOCATE="y" @@ -813,7 +808,7 @@ RUN_TEST_ASAN_OPTIONS="${RUN_TEST_ASAN_OPTIONS}detect_leaks=0" if [ "$EXTERNAL_LOG_TAGS" = "n" ]; then if [ "$DEV_MODE" = "y" ]; then export ANDROID_LOG_TAGS='*:d' - elif [ "$USE_DEX2OAT_AND_PATCHOAT" = "n" ]; then + elif [ "$USE_PATCHOAT" = "n" ]; then # All tests would log the error of failing dex2oat/patchoat. Be silent here and only # log fatal events. export ANDROID_LOG_TAGS='*:s' diff --git a/test/knownfailures.json b/test/knownfailures.json index f71e9363cb..0a179c7093 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -76,7 +76,7 @@ }, { "tests" : "629-vdex-speed", - "variant": "interp-ac | no-dex2oat | interpreter | jit | relocate-npatchoat", + "variant": "interp-ac | interpreter | jit | relocate-npatchoat", "description": "629 requires compilation." }, { @@ -163,7 +163,7 @@ }, { "tests": "147-stripped-dex-fallback", - "variant": "no-dex2oat | no-image | relocate-npatchoat", + "variant": "no-image | relocate-npatchoat", "description": ["147-stripped-dex-fallback is disabled because it", "requires --prebuild."] }, @@ -174,7 +174,7 @@ "119-noimage-patchoat", "137-cfi", "138-duplicate-classes-check2"], - "variant": "no-dex2oat | no-image | relocate-npatchoat", + "variant": "no-image | relocate-npatchoat", "description": ["All these tests check that we have sane behavior if we", "don't have a patchoat or dex2oat. Therefore we", "shouldn't run them in situations where we actually", @@ -649,9 +649,9 @@ }, { "tests": "660-clinit", - "variant": "no-image | no-dex2oat | no-prebuild | jvmti-stress | redefine-stress", + "variant": "no-image | no-prebuild | jvmti-stress | redefine-stress", "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild, ", - "--no-dex2oat, and --redefine-stress do not create"] + "and --redefine-stress do not create"] }, { "tests": ["961-default-iface-resolution-gen", @@ -674,7 +674,7 @@ }, { "tests": "661-oat-writer-layout", - "variant": "interp-ac | interpreter | jit | no-dex2oat | no-prebuild | no-image | trace | redefine-stress | jvmti-stress", + "variant": "interp-ac | interpreter | jit | no-prebuild | no-image | trace | redefine-stress | jvmti-stress", "description": ["Test is designed to only check --compiler-filter=speed"] }, { @@ -1010,7 +1010,7 @@ }, { "tests": "677-fsi", - "variant": "no-dex2oat | no-image | no-prebuild | relocate-npatchoat | jvm", + "variant": "no-image | no-prebuild | relocate-npatchoat | jvm", "description": ["Test requires a successful dex2oat invocation"] }, { diff --git a/test/run-test b/test/run-test index d90eccdf75..ef173026c1 100755 --- a/test/run-test +++ b/test/run-test @@ -148,7 +148,6 @@ jvmti_redefine_stress="false" strace="false" always_clean="no" never_clean="no" -have_dex2oat="yes" have_patchoat="yes" have_image="yes" multi_image_suffix="" @@ -195,9 +194,6 @@ while true; do lib="libdvm.so" runtime="dalvik" shift - elif [ "x$1" = "x--no-dex2oat" ]; then - have_dex2oat="no" - shift elif [ "x$1" = "x--no-patchoat" ]; then have_patchoat="no" shift @@ -580,10 +576,6 @@ if [ "$have_patchoat" = "no" ]; then run_args="${run_args} --no-patchoat" fi -if [ "$have_dex2oat" = "no" ]; then - run_args="${run_args} --no-dex2oat" -fi - if [ ! "$runtime" = "jvm" ]; then run_args="${run_args} --lib $lib" fi @@ -639,11 +631,6 @@ if [ "$bisection_search" = "yes" -a "$prebuild_mode" = "yes" ]; then usage="yes" fi -if [ "$bisection_search" = "yes" -a "$have_dex2oat" = "no" ]; then - err_echo "--bisection-search and --no-dex2oat are mutually exclusive" - usage="yes" -fi - if [ "$bisection_search" = "yes" -a "$have_patchoat" = "no" ]; then err_echo "--bisection-search and --no-patchoat are mutually exclusive" usage="yes" diff --git a/test/testrunner/target_config.py b/test/testrunner/target_config.py index 47b1e4e261..84490bf0e4 100644 --- a/test/testrunner/target_config.py +++ b/test/testrunner/target_config.py @@ -184,7 +184,7 @@ target_config = { 'run-test' : ['--relocate-npatchoat'] }, 'art-no-dex2oat' : { - 'run-test' : ['--no-dex2oat'] + # Deprecated configuration. }, 'art-heap-poisoning' : { 'run-test' : ['--interpreter', diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index e8d4290d28..10c8619307 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -145,7 +145,7 @@ def gather_test_info(): VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image', 'multipicimage'} VARIANT_TYPE_DICT['debuggable'] = {'ndebuggable', 'debuggable'} VARIANT_TYPE_DICT['gc'] = {'gcstress', 'gcverify', 'cms'} - VARIANT_TYPE_DICT['prebuild'] = {'no-prebuild', 'no-dex2oat', 'prebuild'} + VARIANT_TYPE_DICT['prebuild'] = {'no-prebuild', 'prebuild'} VARIANT_TYPE_DICT['cdex_level'] = {'cdex-none', 'cdex-fast'} VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'} VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} @@ -414,8 +414,6 @@ def run_tests(tests): options_test += ' --prebuild' elif prebuild == 'no-prebuild': options_test += ' --no-prebuild' - elif prebuild == 'no-dex2oat': - options_test += ' --no-prebuild --no-dex2oat' if cdex_level: # Add option and remove the cdex- prefix. diff --git a/tools/art_verifier/art_verifier.cc b/tools/art_verifier/art_verifier.cc index bb43e67bb6..8f412bf7a6 100644 --- a/tools/art_verifier/art_verifier.cc +++ b/tools/art_verifier/art_verifier.cc @@ -111,6 +111,9 @@ struct MethodVerifierArgs : public CmdlineArgs { } else if (option.starts_with("--repetitions=")) { char* end; repetitions_ = strtoul(option.substr(strlen("--repetitions=")).data(), &end, 10); + } else if (option.starts_with("--api-level=")) { + char* end; + api_level_ = strtoul(option.substr(strlen("--api-level=")).data(), &end, 10); } else { return kParseUnknownArgument; } @@ -146,6 +149,7 @@ struct MethodVerifierArgs : public CmdlineArgs { " --verbose: use verbose verifier mode.\n" " --verbose-debug: use verbose verifier debug mode.\n" " --repetitions=<count>: repeat the verification count times.\n" + " --api-level=<level>: use API level for verification.\n" "\n"; usage += Base::GetUsage(); @@ -162,6 +166,8 @@ struct MethodVerifierArgs : public CmdlineArgs { bool method_verifier_verbose_debug_ = false; size_t repetitions_ = 0u; + + uint32_t api_level_ = 0u; }; struct MethodVerifierMain : public CmdlineMain<MethodVerifierArgs> { @@ -241,6 +247,7 @@ struct MethodVerifierMain : public CmdlineMain<MethodVerifierArgs> { runtime->GetCompilerCallbacks(), true, verifier::HardFailLogMode::kLogWarning, + args_->api_level_, &error_msg); if (args_->repetitions_ == 0) { LOG(INFO) << descriptor << ": " << res << " " << error_msg; diff --git a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java index 694a5f4a3f..92620760e7 100644 --- a/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java +++ b/tools/class2greylist/src/com/android/class2greylist/Class2Greylist.java @@ -86,6 +86,12 @@ public class Class2Greylist { .withDescription("Enable debug") .create("d")); options.addOption(OptionBuilder + .withLongOpt("dump-all-members") + .withDescription("Dump all members from jar files to stdout. Ignore annotations. " + + "Do not use in conjunction with any other arguments.") + .hasArgs(0) + .create('m')); + options.addOption(OptionBuilder .withLongOpt("help") .hasArgs(0) .withDescription("Show this help") @@ -113,16 +119,21 @@ public class Class2Greylist { } Status status = new Status(cmd.hasOption('d')); - try { - Class2Greylist c2gl = new Class2Greylist( - status, - cmd.getOptionValue('p', null), - cmd.getOptionValues('g'), - cmd.getOptionValue('w', null), - jarFiles); - c2gl.main(); - } catch (IOException e) { - status.error(e); + + if (cmd.hasOption('m')) { + dumpAllMembers(status, jarFiles); + } else { + try { + Class2Greylist c2gl = new Class2Greylist( + status, + cmd.getOptionValue('p', null), + cmd.getOptionValues('g'), + cmd.getOptionValue('w', null), + jarFiles); + c2gl.main(); + } catch (IOException e) { + status.error(e); + } } if (status.ok()) { @@ -221,6 +232,20 @@ public class Class2Greylist { return map; } + private static void dumpAllMembers(Status status, String[] jarFiles) { + for (String jarFile : jarFiles) { + status.debug("Processing jar file %s", jarFile); + try { + JarReader reader = new JarReader(status, jarFile); + reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status) + .visit()); + reader.close(); + } catch (IOException e) { + status.error(e); + } + } + } + private static void help(Options options) { new HelpFormatter().printHelp( "class2greylist path/to/classes.jar [classes2.jar ...]", diff --git a/tools/class2greylist/src/com/android/class2greylist/MemberDumpingVisitor.java b/tools/class2greylist/src/com/android/class2greylist/MemberDumpingVisitor.java new file mode 100644 index 0000000000..6677a3f3ab --- /dev/null +++ b/tools/class2greylist/src/com/android/class2greylist/MemberDumpingVisitor.java @@ -0,0 +1,47 @@ +package com.android.class2greylist; + +import org.apache.bcel.classfile.DescendingVisitor; +import org.apache.bcel.classfile.EmptyVisitor; +import org.apache.bcel.classfile.Field; +import org.apache.bcel.classfile.FieldOrMethod; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; + +/** + * A class file visitor that simply prints to stdout the signature of every member within the class. + */ +public class MemberDumpingVisitor extends EmptyVisitor { + + private final Status mStatus; + private final DescendingVisitor mDescendingVisitor; + + /** + * Creates a visitor for a class. + * + * @param clazz Class to visit + */ + public MemberDumpingVisitor(JavaClass clazz, Status status) { + mStatus = status; + mDescendingVisitor = new DescendingVisitor(clazz, this); + } + + public void visit() { + mDescendingVisitor.visit(); + } + + @Override + public void visitMethod(Method method) { + visitMember(method, "L%s;->%s%s"); + } + + @Override + public void visitField(Field field) { + visitMember(field, "L%s;->%s:%s"); + } + + private void visitMember(FieldOrMethod member, String signatureFormatString) { + AnnotationContext context = new AnnotationContext(mStatus, member, + (JavaClass) mDescendingVisitor.predecessor(), signatureFormatString); + System.out.println(context.getMemberDescriptor()); + } +} diff --git a/tools/field-null-percent/Android.bp b/tools/field-null-percent/Android.bp new file mode 100644 index 0000000000..26bb1dc437 --- /dev/null +++ b/tools/field-null-percent/Android.bp @@ -0,0 +1,56 @@ +// +// 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: "fieldnull-defaults", + host_supported: true, + srcs: ["fieldnull.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", + + shared_libs: [ + "libbase", + ], + header_libs: [ + "libopenjdkjvmti_headers", + ], + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + symlink_preferred_arch: true, +} + +art_cc_library { + name: "libfieldnull", + defaults: ["fieldnull-defaults"], +} + +art_cc_library { + name: "libfieldnulld", + defaults: [ + "art_debug_defaults", + "fieldnull-defaults", + ], +} diff --git a/tools/field-null-percent/README.md b/tools/field-null-percent/README.md new file mode 100644 index 0000000000..d8bc65d9cc --- /dev/null +++ b/tools/field-null-percent/README.md @@ -0,0 +1,51 @@ +# fieldnull + +fieldnull is a JVMTI agent designed for testing for a given field the number of +instances with that field set to null. This can be useful for determining what +fields should be moved into side structures in cases where memory use is +important. + +# Usage +### Build +> `make libfieldnull` + +The libraries will be built for 32-bit, 64-bit, host and target. Below examples +assume you want to use the 64-bit version. + +### Command Line + +The agent is loaded using -agentpath like normal. It takes arguments in the +following format: +> `Lname/of/class;.nameOfField:Ltype/of/field;[,...]` + +#### ART +> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so '-agentpath:libfieldnull.so=Lname/of/class;.nameOfField:Ltype/of/field;' -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/libfieldnull.so /data/local/tmp/` +> +> `adb shell am start-activity --attach-agent '/data/local/tmp/libfieldnull.so=Ljava/lang/Class;.name:Ljava/lang/String;' some.debuggable.apps/.the.app.MainActivity` + +#### RI +> `java '-agentpath:libfieldnull.so=Lname/of/class;.nameOfField:Ltype/of/field;' -cp tmp/helloworld/classes helloworld` + +### Printing the Results +All statistics gathered during the trace are printed automatically when the +program normally exits. In the case of Android applications, they are always +killed, so we need to manually print the results. + +> `kill -SIGQUIT $(pid com.littleinc.orm_benchmark)` + +Will initiate a dump of the counts (to logcat). + +The dump will look something like this. + +> `dalvikvm32 I 08-30 14:51:20 84818 84818 fieldnull.cc:96] Dumping counts of null fields.` +> +> `dalvikvm32 I 08-30 14:51:20 84818 84818 fieldnull.cc:97] Field name null count total count` +> +> `dalvikvm32 I 08-30 14:51:20 84818 84818 fieldnull.cc:135] Ljava/lang/Class;.name:Ljava/lang/String; 5 2936` diff --git a/tools/field-null-percent/fieldnull.cc b/tools/field-null-percent/fieldnull.cc new file mode 100644 index 0000000000..86459d238b --- /dev/null +++ b/tools/field-null-percent/fieldnull.cc @@ -0,0 +1,218 @@ +// 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 <iomanip> +#include <iostream> +#include <istream> +#include <jni.h> +#include <jvmti.h> +#include <memory> +#include <sstream> +#include <string.h> +#include <string> +#include <vector> + +namespace fieldnull { + +#define CHECK_JVMTI(x) CHECK_EQ((x), JVMTI_ERROR_NONE) + +// 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; + +static JavaVM* java_vm = nullptr; + +// Field is "Lclass/name/here;.field_name:Lfield/type/here;" +static std::pair<jclass, jfieldID> SplitField(JNIEnv* env, const std::string& field_id) { + CHECK_EQ(field_id[0], 'L'); + env->PushLocalFrame(1); + std::istringstream is(field_id); + std::string class_name; + std::string field_name; + std::string field_type; + + std::getline(is, class_name, '.'); + std::getline(is, field_name, ':'); + std::getline(is, field_type, '\0'); + + jclass klass = reinterpret_cast<jclass>( + env->NewGlobalRef(env->FindClass(class_name.substr(1, class_name.size() - 2).c_str()))); + jfieldID field = env->GetFieldID(klass, field_name.c_str(), field_type.c_str()); + CHECK(klass != nullptr); + CHECK(field != nullptr); + LOG(INFO) << "listing field " << field_id; + env->PopLocalFrame(nullptr); + return std::make_pair(klass, field); +} + +static std::vector<std::pair<jclass, jfieldID>> GetRequestedFields(JNIEnv* env, + const std::string& args) { + std::vector<std::pair<jclass, jfieldID>> res; + std::stringstream args_stream(args); + std::string item; + while (std::getline(args_stream, item, ',')) { + if (item == "") { + continue; + } + res.push_back(SplitField(env, item)); + } + return res; +} + +static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) { + jint res = 0; + 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; +} + +struct RequestList { + std::vector<std::pair<jclass, jfieldID>> fields_; +}; + +static void DataDumpRequestCb(jvmtiEnv* jvmti) { + JNIEnv* env = nullptr; + CHECK_EQ(java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6), JNI_OK); + LOG(INFO) << "Dumping counts of null fields."; + LOG(INFO) << "\t" << "Field name" + << "\t" << "null count" + << "\t" << "total count"; + RequestList* list; + CHECK_JVMTI(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&list))); + for (std::pair<jclass, jfieldID>& p : list->fields_) { + jclass klass = p.first; + jfieldID field = p.second; + // Make sure all instances of the class are tagged with the klass ptr value. Since this is a + // global ref it's guaranteed to be unique. + CHECK_JVMTI(jvmti->IterateOverInstancesOfClass( + p.first, + // We need to do this to all objects every time since we might be looking for multiple + // fields in classes that are subtypes of each other. + JVMTI_HEAP_OBJECT_EITHER, + /* class_tag, size, tag_ptr, user_data*/ + [](jlong, jlong, jlong* tag_ptr, void* klass) -> jvmtiIterationControl { + *tag_ptr = static_cast<jlong>(reinterpret_cast<intptr_t>(klass)); + return JVMTI_ITERATION_CONTINUE; + }, + klass)); + jobject* obj_list; + jint obj_len; + jlong tag = static_cast<jlong>(reinterpret_cast<intptr_t>(klass)); + CHECK_JVMTI(jvmti->GetObjectsWithTags(1, &tag, &obj_len, &obj_list, nullptr)); + + uint64_t null_cnt = 0; + for (jint i = 0; i < obj_len; i++) { + if (env->GetObjectField(obj_list[i], field) == nullptr) { + null_cnt++; + } + } + + char* field_name; + char* field_sig; + char* class_name; + CHECK_JVMTI(jvmti->GetFieldName(klass, field, &field_name, &field_sig, nullptr)); + CHECK_JVMTI(jvmti->GetClassSignature(klass, &class_name, nullptr)); + LOG(INFO) << "\t" << class_name << "." << field_name << ":" << field_sig + << "\t" << null_cnt + << "\t" << obj_len; + CHECK_JVMTI(jvmti->Deallocate(reinterpret_cast<unsigned char*>(field_name))); + CHECK_JVMTI(jvmti->Deallocate(reinterpret_cast<unsigned char*>(field_sig))); + CHECK_JVMTI(jvmti->Deallocate(reinterpret_cast<unsigned char*>(class_name))); + } +} + +static void VMDeathCb(jvmtiEnv* jvmti, JNIEnv* env ATTRIBUTE_UNUSED) { + DataDumpRequestCb(jvmti); + RequestList* list = nullptr; + CHECK_JVMTI(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&list))); + delete list; +} + +static void CreateFieldList(jvmtiEnv* jvmti, JNIEnv* env, std::string args) { + RequestList* list = nullptr; + CHECK_JVMTI(jvmti->Allocate(sizeof(*list), reinterpret_cast<unsigned char**>(&list))); + new (list) RequestList { .fields_ = GetRequestedFields(env, args), }; + CHECK_JVMTI(jvmti->SetEnvironmentLocalStorage(list)); +} + +static void VMInitCb(jvmtiEnv* jvmti, JNIEnv* env, jobject thr ATTRIBUTE_UNUSED) { + char* args = nullptr; + CHECK_JVMTI(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&args))); + CHECK_JVMTI(jvmti->SetEnvironmentLocalStorage(nullptr)); + CreateFieldList(jvmti, env, args); + CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr)); + CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_DATA_DUMP_REQUEST, + nullptr)); + CHECK_JVMTI(jvmti->Deallocate(reinterpret_cast<unsigned char*>(args))); +} + +static jint AgentStart(JavaVM* vm, char* options, bool is_onload) { + android::base::InitLogging(/* argv */nullptr); + java_vm = vm; + jvmtiEnv* jvmti = nullptr; + if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) { + LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!"; + return JNI_ERR; + } + jvmtiCapabilities caps { .can_tag_objects = 1, }; + CHECK_JVMTI(jvmti->AddCapabilities(&caps)); + jvmtiEventCallbacks cb { + .VMInit = VMInitCb, + .DataDumpRequest = DataDumpRequestCb, + .VMDeath = VMDeathCb, + }; + CHECK_JVMTI(jvmti->SetEventCallbacks(&cb, sizeof(cb))); + if (is_onload) { + unsigned char* ptr = nullptr; + CHECK_JVMTI(jvmti->Allocate(strlen(options) + 1, &ptr)); + strcpy(reinterpret_cast<char*>(ptr), options); + CHECK_JVMTI(jvmti->SetEnvironmentLocalStorage(ptr)); + CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr)); + } else { + JNIEnv* env = nullptr; + CHECK_EQ(vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6), JNI_OK); + CreateFieldList(jvmti, env, options); + CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr)); + CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_DATA_DUMP_REQUEST, + nullptr)); + } + return JNI_OK; +} + +// Late attachment (e.g. 'am attach-agent'). +extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, + char* options, + void* reserved ATTRIBUTE_UNUSED) { + return AgentStart(vm, options, /*is_onload*/false); +} + +// Early attachment +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, + char* options, + void* reserved ATTRIBUTE_UNUSED) { + return AgentStart(jvm, options, /*is_onload*/true); +} + +} // namespace fieldnull + diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt index 942a4e0fc6..23533af02b 100644 --- a/tools/libcore_gcstress_debug_failures.txt +++ b/tools/libcore_gcstress_debug_failures.txt @@ -10,14 +10,26 @@ modes: [device], names: ["jsr166.CompletableFutureTest#testCompleteOnTimeout_completed", "jsr166.CompletableFutureTest#testDelayedExecutor", + "jsr166.ExecutorsTest#testTimedCallable", + "jsr166.RecursiveActionTest#testJoinIgnoresInterruptsOutsideForkJoinPool", "libcore.libcore.icu.TransliteratorTest#testAll", "libcore.libcore.icu.RelativeDateTimeFormatterTest#test_bug25821045", "libcore.libcore.icu.RelativeDateTimeFormatterTest#test_bug25883157", "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndTimeout", + "libcore.java.text.DecimalFormatTest#testWhitespaceError", + "libcore.java.text.DecimalFormatTest#testWhitespaceTolerated", + "libcore.java.text.DecimalFormatTest#test_exponentSeparator", + "libcore.java.text.DecimalFormatTest#test_setMaximumFractionDigitsAffectsRoundingMode", + "libcore.java.util.jar.OldJarFileTest#test_ConstructorLjava_io_File", + "libcore.java.util.jar.OldJarFileTest#test_ConstructorLjava_lang_StringZ", + "libcore.java.util.jar.OldJarInputStreamTest#test_read$ZII", "libcore.java.util.TimeZoneTest#testSetDefaultDeadlock", "libcore.javax.crypto.CipherBasicsTest#testBasicEncryption", + "org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_removeJ", "org.apache.harmony.tests.java.text.MessageFormatTest#test_parseLjava_lang_String", - "org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread"] + "org.apache.harmony.tests.java.util.ControlTest#test_toBundleName_LStringLLocale", + "org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread" + ] }, { description: "Sometimes times out with gcstress and debug.", |