/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "base/globals.h" #include "base/stl_util.h" #include "class_linker.h" #include "dex/utf.h" #include "dexopt_test.h" #include "intern_table-inl.h" #include "noop_compiler_callbacks.h" #include "oat_file.h" namespace art { namespace gc { namespace space { class ImageSpaceTest : public CommonRuntimeTest { protected: void SetUpRuntimeOptions(RuntimeOptions* options) override { // Disable relocation. options->emplace_back("-Xnorelocate", nullptr); } std::string GetFilenameBase(const std::string& full_path) { size_t slash_pos = full_path.rfind('/'); CHECK_NE(std::string::npos, slash_pos); size_t dot_pos = full_path.rfind('.'); CHECK_NE(std::string::npos, dot_pos); CHECK_GT(dot_pos, slash_pos + 1u); return full_path.substr(slash_pos + 1u, dot_pos - (slash_pos + 1u)); } }; TEST_F(ImageSpaceTest, StringDeduplication) { const char* const kBaseNames[] = { "Extension1", "Extension2" }; ScratchDir scratch; const std::string& scratch_dir = scratch.GetPath(); std::string image_dir = scratch_dir + GetInstructionSetString(kRuntimeISA); int mkdir_result = mkdir(image_dir.c_str(), 0700); ASSERT_EQ(0, mkdir_result); // Prepare boot class path variables, exclude core-icu4j and conscrypt // which are not in the primary boot image. std::vector bcp = GetLibCoreDexFileNames(); std::vector bcp_locations = GetLibCoreDexLocations(); CHECK_EQ(bcp.size(), bcp_locations.size()); ASSERT_NE(std::string::npos, bcp.back().find("conscrypt")); bcp.pop_back(); bcp_locations.pop_back(); ASSERT_NE(std::string::npos, bcp.back().find("core-icu4j")); bcp.pop_back(); bcp_locations.pop_back(); std::string base_bcp_string = android::base::Join(bcp, ':'); std::string base_bcp_locations_string = android::base::Join(bcp_locations, ':'); std::string base_image_location = GetImageLocation(); // Compile the two extensions independently. std::vector extension_image_locations; for (const char* base_name : kBaseNames) { std::string jar_name = GetTestDexFileName(base_name); ArrayRef dex_files(&jar_name, /*size=*/ 1u); ScratchFile profile_file; GenerateBootProfile(dex_files, profile_file.GetFile()); std::vector extra_args = { "--profile-file=" + profile_file.GetFilename(), "--runtime-arg", "-Xbootclasspath:" + base_bcp_string + ':' + jar_name, "--runtime-arg", "-Xbootclasspath-locations:" + base_bcp_locations_string + ':' + jar_name, "--boot-image=" + base_image_location, }; std::string prefix = GetFilenameBase(base_image_location); std::string error_msg; bool success = CompileBootImage(extra_args, image_dir + '/' + prefix, dex_files, &error_msg); ASSERT_TRUE(success) << error_msg; bcp.push_back(jar_name); bcp_locations.push_back(jar_name); extension_image_locations.push_back( scratch_dir + prefix + '-' + GetFilenameBase(jar_name) + ".art"); } // Also compile the second extension as an app with app image. const char* app_base_name = kBaseNames[std::size(kBaseNames) - 1u]; std::string app_jar_name = GetTestDexFileName(app_base_name); std::string app_odex_name = scratch_dir + app_base_name + ".odex"; std::string app_image_name = scratch_dir + app_base_name + ".art"; { ArrayRef dex_files(&app_jar_name, /*size=*/ 1u); ScratchFile profile_file; GenerateProfile(dex_files, profile_file.GetFile()); std::vector argv; std::string error_msg; bool success = StartDex2OatCommandLine(&argv, &error_msg, /*use_runtime_bcp_and_image=*/ false); ASSERT_TRUE(success) << error_msg; argv.insert(argv.end(), { "--profile-file=" + profile_file.GetFilename(), "--runtime-arg", "-Xbootclasspath:" + base_bcp_string, "--runtime-arg", "-Xbootclasspath-locations:" + base_bcp_locations_string, "--boot-image=" + base_image_location, "--dex-file=" + app_jar_name, "--dex-location=" + app_jar_name, "--oat-file=" + app_odex_name, "--app-image-file=" + app_image_name, "--initialize-app-image-classes=true", }); success = RunDex2Oat(argv, &error_msg); ASSERT_TRUE(success) << error_msg; } std::vector full_image_locations; std::vector> boot_image_spaces; MemMap extra_reservation; auto load_boot_image = [&]() REQUIRES_SHARED(Locks::mutator_lock_) { boot_image_spaces.clear(); extra_reservation = MemMap::Invalid(); return ImageSpace::LoadBootImage(bcp, bcp_locations, /*boot_class_path_fds=*/ std::vector(), /*boot_class_path_image_fds=*/ std::vector(), /*boot_class_path_vdex_fds=*/ std::vector(), /*boot_class_path_oat_fds=*/ std::vector(), full_image_locations, kRuntimeISA, /*relocate=*/ false, /*executable=*/ true, /*extra_reservation_size=*/ 0u, &boot_image_spaces, &extra_reservation); }; const char test_string[] = "SharedBootImageExtensionTestString"; size_t test_string_length = std::size(test_string) - 1u; // Equals UTF-16 length. uint32_t hash = InternTable::Utf8String::Hash(test_string_length, test_string); InternTable::Utf8String utf8_test_string(test_string_length, test_string); auto contains_test_string = [utf8_test_string, hash](ImageSpace* space) REQUIRES_SHARED(Locks::mutator_lock_) { const ImageHeader& image_header = space->GetImageHeader(); if (image_header.GetInternedStringsSection().Size() != 0u) { const uint8_t* data = space->Begin() + image_header.GetInternedStringsSection().Offset(); size_t read_count; InternTable::UnorderedSet temp_set(data, /*make_copy_of_data=*/ false, &read_count); return temp_set.FindWithHash(utf8_test_string, hash) != temp_set.end(); } else { return false; } }; // Load extensions and test for the presence of the test string. ScopedObjectAccess soa(Thread::Current()); ASSERT_EQ(2u, extension_image_locations.size()); full_image_locations = { base_image_location, extension_image_locations[0], extension_image_locations[1] }; bool success = load_boot_image(); ASSERT_TRUE(success); ASSERT_EQ(bcp.size(), boot_image_spaces.size()); EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get())); // The string in the second extension should be replaced and removed from interned string section. EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get())); // Reload extensions in reverse order and test for the presence of the test string. std::swap(bcp[bcp.size() - 2u], bcp[bcp.size() - 1u]); std::swap(bcp_locations[bcp_locations.size() - 2u], bcp_locations[bcp_locations.size() - 1u]); full_image_locations = { base_image_location, extension_image_locations[1], extension_image_locations[0] }; success = load_boot_image(); ASSERT_TRUE(success); ASSERT_EQ(bcp.size(), boot_image_spaces.size()); EXPECT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 2u].get())); // The string in the second extension should be replaced and removed from interned string section. EXPECT_FALSE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get())); // Reload the image without the second extension. bcp.erase(bcp.end() - 2u); bcp_locations.erase(bcp_locations.end() - 2u); full_image_locations = {base_image_location, extension_image_locations[0]}; success = load_boot_image(); ASSERT_TRUE(success); ASSERT_EQ(bcp.size(), boot_image_spaces.size()); ASSERT_TRUE(contains_test_string(boot_image_spaces[boot_image_spaces.size() - 1u].get())); // Load the app odex file and app image. std::string error_msg; std::unique_ptr odex_file(OatFile::Open(/*zip_fd=*/ -1, app_odex_name.c_str(), app_odex_name.c_str(), /*executable=*/ false, /*low_4gb=*/ false, app_jar_name, &error_msg)); ASSERT_TRUE(odex_file != nullptr) << error_msg; std::vector non_owning_boot_image_spaces = MakeNonOwningPointerVector(boot_image_spaces); std::unique_ptr app_image_space = ImageSpace::CreateFromAppImage( app_image_name.c_str(), odex_file.get(), ArrayRef(non_owning_boot_image_spaces), &error_msg); ASSERT_TRUE(app_image_space != nullptr) << error_msg; // The string in the app image should be replaced and removed from interned string section. EXPECT_FALSE(contains_test_string(app_image_space.get())); } TEST_F(DexoptTest, ValidateOatFile) { std::string dex1 = GetScratchDir() + "/Dex1.jar"; std::string multidex1 = GetScratchDir() + "/MultiDex1.jar"; std::string dex2 = GetScratchDir() + "/Dex2.jar"; std::string oat_location = GetScratchDir() + "/Oat.oat"; Copy(GetDexSrc1(), dex1); Copy(GetMultiDexSrc1(), multidex1); Copy(GetDexSrc2(), dex2); std::string error_msg; std::vector args; args.push_back("--dex-file=" + dex1); args.push_back("--dex-file=" + multidex1); args.push_back("--dex-file=" + dex2); args.push_back("--oat-file=" + oat_location); ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; std::unique_ptr oat(OatFile::Open(/*zip_fd=*/ -1, oat_location.c_str(), oat_location.c_str(), /*executable=*/ false, /*low_4gb=*/ false, &error_msg)); ASSERT_TRUE(oat != nullptr) << error_msg; { // Test opening the oat file also with explicit dex filenames. std::vector dex_filenames{ dex1, multidex1, dex2 }; std::unique_ptr oat2(OatFile::Open(/*zip_fd=*/ -1, oat_location.c_str(), oat_location.c_str(), /*executable=*/ false, /*low_4gb=*/ false, ArrayRef(dex_filenames), /*dex_fds=*/ ArrayRef(), /*reservation=*/ nullptr, &error_msg)); ASSERT_TRUE(oat2 != nullptr) << error_msg; } // Originally all the dex checksums should be up to date. EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Invalidate the dex1 checksum. Copy(GetDexSrc2(), dex1); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // Restore the dex1 checksum. Copy(GetDexSrc1(), dex1); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Invalidate the non-main multidex checksum. Copy(GetMultiDexSrc2(), multidex1); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // Restore the multidex checksum. Copy(GetMultiDexSrc1(), multidex1); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Invalidate the dex2 checksum. Copy(GetDexSrc1(), dex2); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // restore the dex2 checksum. Copy(GetDexSrc2(), dex2); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Replace the multidex file with a non-multidex file. Copy(GetDexSrc1(), multidex1); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // Restore the multidex file Copy(GetMultiDexSrc1(), multidex1); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Replace dex1 with a multidex file. Copy(GetMultiDexSrc1(), dex1); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // Restore the dex1 file. Copy(GetDexSrc1(), dex1); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Remove the dex2 file. EXPECT_EQ(0, unlink(dex2.c_str())); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); // Restore the dex2 file. Copy(GetDexSrc2(), dex2); EXPECT_TRUE(ImageSpace::ValidateOatFile(*oat, &error_msg)) << error_msg; // Remove the multidex file. EXPECT_EQ(0, unlink(multidex1.c_str())); EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg)); } TEST_F(DexoptTest, Checksums) { Runtime* runtime = Runtime::Current(); ASSERT_TRUE(runtime != nullptr); ASSERT_FALSE(runtime->GetHeap()->GetBootImageSpaces().empty()); std::vector bcp = runtime->GetBootClassPath(); std::vector bcp_locations = runtime->GetBootClassPathLocations(); std::vector dex_files = runtime->GetClassLinker()->GetBootClassPath(); std::string error_msg; auto create_and_verify = [&]() { std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums( ArrayRef(runtime->GetHeap()->GetBootImageSpaces()), ArrayRef(dex_files)); return gc::space::ImageSpace::VerifyBootClassPathChecksums( checksums, android::base::Join(bcp_locations, ':'), ArrayRef(runtime->GetImageLocations()), ArrayRef(bcp_locations), ArrayRef(bcp), /*boot_class_path_fds=*/ ArrayRef(), kRuntimeISA, &error_msg); }; ASSERT_TRUE(create_and_verify()) << error_msg; std::vector> opened_dex_files; for (const std::string& src : { GetDexSrc1(), GetDexSrc2() }) { std::vector> new_dex_files; const ArtDexFileLoader dex_file_loader; ASSERT_TRUE(dex_file_loader.Open(src.c_str(), src, /*verify=*/ true, /*verify_checksum=*/ false, &error_msg, &new_dex_files)) << error_msg; bcp.push_back(src); bcp_locations.push_back(src); for (std::unique_ptr& df : new_dex_files) { dex_files.push_back(df.get()); opened_dex_files.push_back(std::move(df)); } ASSERT_TRUE(create_and_verify()) << error_msg; } } template class ImageSpaceLoadingTest : public CommonRuntimeTest { protected: void SetUpRuntimeOptions(RuntimeOptions* options) override { missing_image_base_ = std::make_unique(); std::string image_location = PrepareImageLocation(); options->emplace_back(android::base::StringPrintf("-Ximage:%s", image_location.c_str()), nullptr); options->emplace_back(kRelocate ? "-Xrelocate" : "-Xnorelocate", nullptr); // We want to test the relocation behavior of ImageSpace. As such, don't pretend we're a // compiler. callbacks_.reset(); // Clear DEX2OATBOOTCLASSPATH environment variable used for boot image compilation. // We don't want that environment variable to affect the behavior of this test. CHECK(old_dex2oat_bcp_ == nullptr); const char* old_dex2oat_bcp = getenv("DEX2OATBOOTCLASSPATH"); if (old_dex2oat_bcp != nullptr) { old_dex2oat_bcp_.reset(strdup(old_dex2oat_bcp)); CHECK(old_dex2oat_bcp_ != nullptr); unsetenv("DEX2OATBOOTCLASSPATH"); } } void TearDown() override { if (old_dex2oat_bcp_ != nullptr) { int result = setenv("DEX2OATBOOTCLASSPATH", old_dex2oat_bcp_.get(), /* replace */ 0); CHECK_EQ(result, 0); old_dex2oat_bcp_.reset(); } missing_image_base_.reset(); } virtual std::string PrepareImageLocation() { std::string image_location = GetCoreArtLocation(); if (!kImage) { image_location = missing_image_base_->GetFilename() + ".art"; } return image_location; } void CheckImageSpaceAndOatFile(size_t space_count) { const std::vector& image_spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces(); ASSERT_EQ(image_spaces.size(), space_count); for (size_t i = 0; i < space_count; i++) { // This test does not support multi-image compilation. ASSERT_NE(image_spaces[i]->GetImageHeader().GetImageReservationSize(), 0u); const OatFile* oat_file = image_spaces[i]->GetOatFile(); ASSERT_TRUE(oat_file != nullptr); // Compiled by JIT Zygote. EXPECT_EQ(oat_file->GetCompilerFilter(), CompilerFilter::Filter::kVerify); } } std::unique_ptr missing_image_base_; private: UniqueCPtr old_dex2oat_bcp_; }; using ImageSpaceNoDex2oatTest = ImageSpaceLoadingTest; TEST_F(ImageSpaceNoDex2oatTest, Test) { EXPECT_FALSE(Runtime::Current()->GetHeap()->GetBootImageSpaces().empty()); } using ImageSpaceNoRelocateNoDex2oatTest = ImageSpaceLoadingTest; TEST_F(ImageSpaceNoRelocateNoDex2oatTest, Test) { EXPECT_FALSE(Runtime::Current()->GetHeap()->GetBootImageSpaces().empty()); } using ImageSpaceNoImageNoProfileTest = ImageSpaceLoadingTest; TEST_F(ImageSpaceNoImageNoProfileTest, Test) { // Imageless mode. EXPECT_TRUE(Runtime::Current()->GetHeap()->GetBootImageSpaces().empty()); } class ImageSpaceLoadingSingleComponentWithProfilesTest : public ImageSpaceLoadingTest { protected: std::string PrepareImageLocation() override { std::string image_location = missing_image_base_->GetFilename() + ".art"; // Compiling the primary boot image into a single image is not allowed on host. if (kIsTargetBuild) { std::vector dex_files(GetLibCoreDexFileNames()); profile1_ = std::make_unique(); GenerateBootProfile(ArrayRef(dex_files), profile1_->GetFile(), /*method_frequency=*/6, /*type_frequency=*/6); profile2_ = std::make_unique(); GenerateBootProfile(ArrayRef(dex_files), profile2_->GetFile(), /*method_frequency=*/8, /*type_frequency=*/8); image_location += "!" + profile1_->GetFilename() + "!" + profile2_->GetFilename(); } // "/path/to/image.art!/path/to/profile1!/path/to/profile2" return image_location; } private: std::unique_ptr profile1_; std::unique_ptr profile2_; }; TEST_F(ImageSpaceLoadingSingleComponentWithProfilesTest, Test) { // Compiling the primary boot image into a single image is not allowed on host. TEST_DISABLED_FOR_HOST(); CheckImageSpaceAndOatFile(/*space_count=*/1); } class ImageSpaceLoadingMultipleComponentsWithProfilesTest : public ImageSpaceLoadingTest { protected: std::string PrepareImageLocation() override { std::vector dex_files(GetLibCoreDexFileNames()); CHECK_GE(dex_files.size(), 2); std::string image_location_1 = missing_image_base_->GetFilename() + ".art"; std::string image_location_2 = missing_image_base_->GetFilename() + "-" + Stem(dex_files[dex_files.size() - 1]) + ".art"; // Compiling the primary boot image into a single image is not allowed on host. if (kIsTargetBuild) { profile1_ = std::make_unique(); GenerateBootProfile(ArrayRef(dex_files).SubArray( /*pos=*/0, /*length=*/dex_files.size() - 1), profile1_->GetFile(), /*method_frequency=*/6, /*type_frequency=*/6); image_location_1 += "!" + profile1_->GetFilename(); profile2_ = std::make_unique(); GenerateBootProfile(ArrayRef(dex_files).SubArray( /*pos=*/dex_files.size() - 1, /*length=*/1), profile2_->GetFile(), /*method_frequency=*/8, /*type_frequency=*/8); image_location_2 += "!" + profile2_->GetFilename(); } // "/path/to/image.art!/path/to/profile1:/path/to/image-lastdex.art!/path/to/profile2" return image_location_1 + ":" + image_location_2; } std::string Stem(std::string filename) { size_t last_slash = filename.rfind('/'); if (last_slash != std::string::npos) { filename = filename.substr(last_slash + 1); } size_t last_dot = filename.rfind('.'); if (last_dot != std::string::npos) { filename.resize(last_dot); } return filename; } private: std::unique_ptr profile1_; std::unique_ptr profile2_; }; TEST_F(ImageSpaceLoadingMultipleComponentsWithProfilesTest, Test) { // Compiling the primary boot image into a single image is not allowed on host. TEST_DISABLED_FOR_HOST(); CheckImageSpaceAndOatFile(/*space_count=*/1); } } // namespace space } // namespace gc } // namespace art