| /* |
| * Copyright (C) 2011 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/stringprintf.h" |
| |
| #include "arch/instruction_set_features.h" |
| #include "art_method-inl.h" |
| #include "base/enums.h" |
| #include "base/file_utils.h" |
| #include "base/stl_util.h" |
| #include "base/unix_file/fd_file.h" |
| #include "class_linker.h" |
| #include "common_compiler_driver_test.h" |
| #include "compiled_method-inl.h" |
| #include "compiler.h" |
| #include "debug/method_debug_info.h" |
| #include "dex/class_accessor-inl.h" |
| #include "dex/dex_file_loader.h" |
| #include "dex/quick_compiler_callbacks.h" |
| #include "dex/test_dex_file_builder.h" |
| #include "dex/verification_results.h" |
| #include "driver/compiler_driver.h" |
| #include "driver/compiler_options.h" |
| #include "entrypoints/quick/quick_entrypoints.h" |
| #include "linker/elf_writer.h" |
| #include "linker/elf_writer_quick.h" |
| #include "linker/multi_oat_relative_patcher.h" |
| #include "mirror/class-inl.h" |
| #include "mirror/object-inl.h" |
| #include "mirror/object_array-inl.h" |
| #include "oat.h" |
| #include "oat_file-inl.h" |
| #include "oat_writer.h" |
| #include "profile/profile_compilation_info.h" |
| #include "scoped_thread_state_change-inl.h" |
| #include "stream/buffered_output_stream.h" |
| #include "stream/file_output_stream.h" |
| #include "stream/vector_output_stream.h" |
| #include "vdex_file.h" |
| |
| namespace art { |
| namespace linker { |
| |
| class OatTest : public CommonCompilerDriverTest { |
| protected: |
| static const bool kCompile = false; // DISABLED_ due to the time to compile libcore |
| |
| void CheckMethod(ArtMethod* method, |
| const OatFile::OatMethod& oat_method, |
| const DexFile& dex_file) |
| REQUIRES_SHARED(Locks::mutator_lock_) { |
| const CompiledMethod* compiled_method = |
| compiler_driver_->GetCompiledMethod(MethodReference(&dex_file, |
| method->GetDexMethodIndex())); |
| |
| if (compiled_method == nullptr) { |
| EXPECT_TRUE(oat_method.GetQuickCode() == nullptr) << method->PrettyMethod() << " " |
| << oat_method.GetQuickCode(); |
| EXPECT_EQ(oat_method.GetFrameSizeInBytes(), 0U); |
| EXPECT_EQ(oat_method.GetCoreSpillMask(), 0U); |
| EXPECT_EQ(oat_method.GetFpSpillMask(), 0U); |
| } else { |
| const void* quick_oat_code = oat_method.GetQuickCode(); |
| EXPECT_TRUE(quick_oat_code != nullptr) << method->PrettyMethod(); |
| uintptr_t oat_code_aligned = RoundDown(reinterpret_cast<uintptr_t>(quick_oat_code), 2); |
| quick_oat_code = reinterpret_cast<const void*>(oat_code_aligned); |
| ArrayRef<const uint8_t> quick_code = compiled_method->GetQuickCode(); |
| EXPECT_FALSE(quick_code.empty()); |
| size_t code_size = quick_code.size() * sizeof(quick_code[0]); |
| EXPECT_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size)) |
| << method->PrettyMethod() << " " << code_size; |
| CHECK_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size)); |
| } |
| } |
| |
| void SetupCompiler(const std::vector<std::string>& compiler_options) { |
| std::string error_msg; |
| if (!compiler_options_->ParseCompilerOptions(compiler_options, |
| /*ignore_unrecognized=*/ false, |
| &error_msg)) { |
| LOG(FATAL) << error_msg; |
| UNREACHABLE(); |
| } |
| callbacks_.reset(new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp)); |
| callbacks_->SetVerificationResults(verification_results_.get()); |
| Runtime::Current()->SetCompilerCallbacks(callbacks_.get()); |
| } |
| |
| bool WriteElf(File* vdex_file, |
| File* oat_file, |
| const std::vector<const DexFile*>& dex_files, |
| SafeMap<std::string, std::string>& key_value_store, |
| bool verify) { |
| TimingLogger timings("WriteElf", false, false); |
| ClearBootImageOption(); |
| OatWriter oat_writer(*compiler_options_, |
| &timings, |
| /*profile_compilation_info*/nullptr, |
| CompactDexLevel::kCompactDexLevelNone); |
| for (const DexFile* dex_file : dex_files) { |
| ArrayRef<const uint8_t> raw_dex_file( |
| reinterpret_cast<const uint8_t*>(&dex_file->GetHeader()), |
| dex_file->GetHeader().file_size_); |
| if (!oat_writer.AddRawDexFileSource(raw_dex_file, |
| dex_file->GetLocation().c_str(), |
| dex_file->GetLocationChecksum())) { |
| return false; |
| } |
| } |
| return DoWriteElf( |
| vdex_file, oat_file, oat_writer, key_value_store, verify, CopyOption::kOnlyIfCompressed); |
| } |
| |
| bool WriteElf(File* vdex_file, |
| File* oat_file, |
| const std::vector<const char*>& dex_filenames, |
| SafeMap<std::string, std::string>& key_value_store, |
| bool verify, |
| CopyOption copy, |
| ProfileCompilationInfo* profile_compilation_info) { |
| TimingLogger timings("WriteElf", false, false); |
| ClearBootImageOption(); |
| OatWriter oat_writer(*compiler_options_, |
| &timings, |
| profile_compilation_info, |
| CompactDexLevel::kCompactDexLevelNone); |
| for (const char* dex_filename : dex_filenames) { |
| if (!oat_writer.AddDexFileSource(dex_filename, dex_filename)) { |
| return false; |
| } |
| } |
| return DoWriteElf(vdex_file, oat_file, oat_writer, key_value_store, verify, copy); |
| } |
| |
| bool WriteElf(File* vdex_file, |
| File* oat_file, |
| File&& dex_file_fd, |
| const char* location, |
| SafeMap<std::string, std::string>& key_value_store, |
| bool verify, |
| CopyOption copy, |
| ProfileCompilationInfo* profile_compilation_info = nullptr) { |
| TimingLogger timings("WriteElf", false, false); |
| ClearBootImageOption(); |
| OatWriter oat_writer(*compiler_options_, |
| &timings, |
| profile_compilation_info, |
| CompactDexLevel::kCompactDexLevelNone); |
| if (!oat_writer.AddDexFileSource(std::move(dex_file_fd), location)) { |
| return false; |
| } |
| return DoWriteElf(vdex_file, oat_file, oat_writer, key_value_store, verify, copy); |
| } |
| |
| bool DoWriteElf(File* vdex_file, |
| File* oat_file, |
| OatWriter& oat_writer, |
| SafeMap<std::string, std::string>& key_value_store, |
| bool verify, |
| CopyOption copy) { |
| std::unique_ptr<ElfWriter> elf_writer = CreateElfWriterQuick( |
| compiler_driver_->GetCompilerOptions(), |
| oat_file); |
| elf_writer->Start(); |
| OutputStream* oat_rodata = elf_writer->StartRoData(); |
| std::vector<MemMap> opened_dex_files_maps; |
| std::vector<std::unique_ptr<const DexFile>> opened_dex_files; |
| if (!oat_writer.WriteAndOpenDexFiles( |
| vdex_file, |
| verify, |
| /*update_input_vdex=*/ false, |
| copy, |
| &opened_dex_files_maps, |
| &opened_dex_files)) { |
| return false; |
| } |
| |
| Runtime* runtime = Runtime::Current(); |
| ClassLinker* const class_linker = runtime->GetClassLinker(); |
| std::vector<const DexFile*> dex_files; |
| for (const std::unique_ptr<const DexFile>& dex_file : opened_dex_files) { |
| dex_files.push_back(dex_file.get()); |
| ScopedObjectAccess soa(Thread::Current()); |
| class_linker->RegisterDexFile(*dex_file, nullptr); |
| } |
| MultiOatRelativePatcher patcher(compiler_options_->GetInstructionSet(), |
| compiler_options_->GetInstructionSetFeatures(), |
| compiler_driver_->GetCompiledMethodStorage()); |
| if (!oat_writer.StartRoData(dex_files, oat_rodata, &key_value_store)) { |
| return false; |
| } |
| oat_writer.Initialize(compiler_driver_.get(), /*image_writer=*/ nullptr, dex_files); |
| oat_writer.PrepareLayout(&patcher); |
| elf_writer->PrepareDynamicSection(oat_writer.GetOatHeader().GetExecutableOffset(), |
| oat_writer.GetCodeSize(), |
| oat_writer.GetDataBimgRelRoSize(), |
| oat_writer.GetBssSize(), |
| oat_writer.GetBssMethodsOffset(), |
| oat_writer.GetBssRootsOffset(), |
| oat_writer.GetVdexSize()); |
| |
| if (!oat_writer.FinishVdexFile(vdex_file, /*verifier_deps=*/ nullptr)) { |
| return false; |
| } |
| |
| if (!oat_writer.WriteRodata(oat_rodata)) { |
| return false; |
| } |
| elf_writer->EndRoData(oat_rodata); |
| |
| OutputStream* text = elf_writer->StartText(); |
| if (!oat_writer.WriteCode(text)) { |
| return false; |
| } |
| elf_writer->EndText(text); |
| |
| if (oat_writer.GetDataBimgRelRoSize() != 0u) { |
| OutputStream* data_bimg_rel_ro = elf_writer->StartDataBimgRelRo(); |
| if (!oat_writer.WriteDataBimgRelRo(data_bimg_rel_ro)) { |
| return false; |
| } |
| elf_writer->EndDataBimgRelRo(data_bimg_rel_ro); |
| } |
| |
| if (!oat_writer.WriteHeader(elf_writer->GetStream())) { |
| return false; |
| } |
| |
| elf_writer->WriteDynamicSection(); |
| elf_writer->WriteDebugInfo(oat_writer.GetDebugInfo()); |
| |
| if (!elf_writer->End()) { |
| return false; |
| } |
| |
| for (MemMap& map : opened_dex_files_maps) { |
| opened_dex_files_maps_.emplace_back(std::move(map)); |
| } |
| for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files) { |
| opened_dex_files_.emplace_back(dex_file.release()); |
| } |
| return true; |
| } |
| |
| void CheckOatWriteResult(ScratchFile& oat_file, |
| ScratchFile& vdex_file, |
| std::vector<std::unique_ptr<const DexFile>>& input_dexfiles, |
| const unsigned int expected_oat_dexfile_count, |
| bool low_4gb) { |
| ASSERT_EQ(expected_oat_dexfile_count, input_dexfiles.size()); |
| |
| std::string error_msg; |
| std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/*zip_fd=*/ -1, |
| oat_file.GetFilename(), |
| oat_file.GetFilename(), |
| /*executable=*/ false, |
| low_4gb, |
| &error_msg)); |
| ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; |
| ASSERT_EQ(expected_oat_dexfile_count, opened_oat_file->GetOatDexFiles().size()); |
| |
| if (low_4gb) { |
| uintptr_t begin = reinterpret_cast<uintptr_t>(opened_oat_file->Begin()); |
| EXPECT_EQ(begin, static_cast<uint32_t>(begin)); |
| } |
| |
| for (uint32_t i = 0; i < input_dexfiles.size(); i++) { |
| const std::unique_ptr<const DexFile>& dex_file_data = input_dexfiles[i]; |
| std::unique_ptr<const DexFile> opened_dex_file = |
| opened_oat_file->GetOatDexFiles()[i]->OpenDexFile(&error_msg); |
| |
| ASSERT_EQ(opened_oat_file->GetOatDexFiles()[i]->GetDexFileLocationChecksum(), |
| dex_file_data->GetHeader().checksum_); |
| |
| ASSERT_EQ(dex_file_data->GetHeader().file_size_, opened_dex_file->GetHeader().file_size_); |
| ASSERT_EQ(0, memcmp(&dex_file_data->GetHeader(), |
| &opened_dex_file->GetHeader(), |
| dex_file_data->GetHeader().file_size_)); |
| ASSERT_EQ(dex_file_data->GetLocation(), opened_dex_file->GetLocation()); |
| } |
| |
| int64_t actual_vdex_size = vdex_file.GetFile()->GetLength(); |
| ASSERT_GE(actual_vdex_size, 0); |
| ASSERT_EQ(dchecked_integral_cast<uint64_t>(actual_vdex_size), |
| opened_oat_file->GetVdexFile()->GetComputedFileSize()); |
| } |
| |
| void TestDexFileInput(bool verify, bool low_4gb, bool use_profile); |
| void TestZipFileInput(bool verify, CopyOption copy); |
| void TestZipFileInputWithEmptyDex(); |
| |
| std::unique_ptr<QuickCompilerCallbacks> callbacks_; |
| |
| std::vector<MemMap> opened_dex_files_maps_; |
| std::vector<std::unique_ptr<const DexFile>> opened_dex_files_; |
| }; |
| |
| class ZipBuilder { |
| public: |
| explicit ZipBuilder(File* zip_file) : zip_file_(zip_file) { } |
| |
| bool AddFile(const char* location, const void* data, size_t size) { |
| off_t offset = lseek(zip_file_->Fd(), 0, SEEK_CUR); |
| if (offset == static_cast<off_t>(-1)) { |
| return false; |
| } |
| |
| ZipFileHeader file_header; |
| file_header.crc32 = crc32(0u, reinterpret_cast<const Bytef*>(data), size); |
| file_header.compressed_size = size; |
| file_header.uncompressed_size = size; |
| file_header.filename_length = strlen(location); |
| |
| if (!zip_file_->WriteFully(&file_header, sizeof(file_header)) || |
| !zip_file_->WriteFully(location, file_header.filename_length) || |
| !zip_file_->WriteFully(data, size)) { |
| return false; |
| } |
| |
| CentralDirectoryFileHeader cdfh; |
| cdfh.crc32 = file_header.crc32; |
| cdfh.compressed_size = size; |
| cdfh.uncompressed_size = size; |
| cdfh.filename_length = file_header.filename_length; |
| cdfh.relative_offset_of_local_file_header = offset; |
| file_data_.push_back(FileData { cdfh, location }); |
| return true; |
| } |
| |
| bool Finish() { |
| off_t offset = lseek(zip_file_->Fd(), 0, SEEK_CUR); |
| if (offset == static_cast<off_t>(-1)) { |
| return false; |
| } |
| |
| size_t central_directory_size = 0u; |
| for (const FileData& file_data : file_data_) { |
| if (!zip_file_->WriteFully(&file_data.cdfh, sizeof(file_data.cdfh)) || |
| !zip_file_->WriteFully(file_data.location, file_data.cdfh.filename_length)) { |
| return false; |
| } |
| central_directory_size += sizeof(file_data.cdfh) + file_data.cdfh.filename_length; |
| } |
| EndOfCentralDirectoryRecord eocd_record; |
| eocd_record.number_of_central_directory_records_on_this_disk = file_data_.size(); |
| eocd_record.total_number_of_central_directory_records = file_data_.size(); |
| eocd_record.size_of_central_directory = central_directory_size; |
| eocd_record.offset_of_start_of_central_directory = offset; |
| return |
| zip_file_->WriteFully(&eocd_record, sizeof(eocd_record)) && |
| zip_file_->Flush() == 0; |
| } |
| |
| private: |
| struct PACKED(1) ZipFileHeader { |
| uint32_t signature = 0x04034b50; |
| uint16_t version_needed_to_extract = 10; |
| uint16_t general_purpose_bit_flag = 0; |
| uint16_t compression_method = 0; // 0 = store only. |
| uint16_t file_last_modification_time = 0u; |
| uint16_t file_last_modification_date = 0u; |
| uint32_t crc32; |
| uint32_t compressed_size; |
| uint32_t uncompressed_size; |
| uint16_t filename_length; |
| uint16_t extra_field_length = 0u; // No extra fields. |
| }; |
| |
| struct PACKED(1) CentralDirectoryFileHeader { |
| uint32_t signature = 0x02014b50; |
| uint16_t version_made_by = 10; |
| uint16_t version_needed_to_extract = 10; |
| uint16_t general_purpose_bit_flag = 0; |
| uint16_t compression_method = 0; // 0 = store only. |
| uint16_t file_last_modification_time = 0u; |
| uint16_t file_last_modification_date = 0u; |
| uint32_t crc32; |
| uint32_t compressed_size; |
| uint32_t uncompressed_size; |
| uint16_t filename_length; |
| uint16_t extra_field_length = 0u; // No extra fields. |
| uint16_t file_comment_length = 0u; // No file comment. |
| uint16_t disk_number_where_file_starts = 0u; |
| uint16_t internal_file_attributes = 0u; |
| uint32_t external_file_attributes = 0u; |
| uint32_t relative_offset_of_local_file_header; |
| }; |
| |
| struct PACKED(1) EndOfCentralDirectoryRecord { |
| uint32_t signature = 0x06054b50; |
| uint16_t number_of_this_disk = 0u; |
| uint16_t disk_where_central_directory_starts = 0u; |
| uint16_t number_of_central_directory_records_on_this_disk; |
| uint16_t total_number_of_central_directory_records; |
| uint32_t size_of_central_directory; |
| uint32_t offset_of_start_of_central_directory; |
| uint16_t comment_length = 0u; // No file comment. |
| }; |
| |
| struct FileData { |
| CentralDirectoryFileHeader cdfh; |
| const char* location; |
| }; |
| |
| File* zip_file_; |
| std::vector<FileData> file_data_; |
| }; |
| |
| TEST_F(OatTest, WriteRead) { |
| TimingLogger timings("OatTest::WriteRead", false, false); |
| ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); |
| |
| std::string error_msg; |
| SetupCompiler(std::vector<std::string>()); |
| |
| jobject class_loader = nullptr; |
| if (kCompile) { |
| TimingLogger timings2("OatTest::WriteRead", false, false); |
| CompileAll(class_loader, class_linker->GetBootClassPath(), &timings2); |
| } |
| |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| SafeMap<std::string, std::string> key_value_store; |
| key_value_store.Put(OatHeader::kBootClassPathChecksumsKey, "testkey"); |
| bool success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| class_linker->GetBootClassPath(), |
| key_value_store, |
| false); |
| ASSERT_TRUE(success); |
| |
| if (kCompile) { // OatWriter strips the code, regenerate to compare |
| CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); |
| } |
| std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, |
| tmp_oat.GetFilename(), |
| tmp_oat.GetFilename(), |
| /*executable=*/ false, |
| /*low_4gb=*/ true, |
| &error_msg)); |
| ASSERT_TRUE(oat_file.get() != nullptr) << error_msg; |
| const OatHeader& oat_header = oat_file->GetOatHeader(); |
| ASSERT_TRUE(oat_header.IsValid()); |
| ASSERT_EQ(class_linker->GetBootClassPath().size(), oat_header.GetDexFileCount()); // core |
| ASSERT_TRUE(oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey) != nullptr); |
| ASSERT_STREQ("testkey", oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey)); |
| |
| ASSERT_TRUE(java_lang_dex_file_ != nullptr); |
| const DexFile& dex_file = *java_lang_dex_file_; |
| uint32_t dex_file_checksum = dex_file.GetLocationChecksum(); |
| const OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_file.GetLocation().c_str(), |
| &dex_file_checksum); |
| ASSERT_TRUE(oat_dex_file != nullptr); |
| CHECK_EQ(dex_file.GetLocationChecksum(), oat_dex_file->GetDexFileLocationChecksum()); |
| ScopedObjectAccess soa(Thread::Current()); |
| auto pointer_size = class_linker->GetImagePointerSize(); |
| for (ClassAccessor accessor : dex_file.GetClasses()) { |
| size_t num_virtual_methods = accessor.NumVirtualMethods(); |
| |
| const char* descriptor = accessor.GetDescriptor(); |
| ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), |
| descriptor, |
| ScopedNullHandle<mirror::ClassLoader>()); |
| |
| const OatFile::OatClass oat_class = oat_dex_file->GetOatClass(accessor.GetClassDefIndex()); |
| CHECK_EQ(ClassStatus::kNotReady, oat_class.GetStatus()) << descriptor; |
| CHECK_EQ(kCompile ? OatClassType::kAllCompiled : OatClassType::kNoneCompiled, |
| oat_class.GetType()) << descriptor; |
| |
| size_t method_index = 0; |
| for (auto& m : klass->GetDirectMethods(pointer_size)) { |
| CheckMethod(&m, oat_class.GetOatMethod(method_index), dex_file); |
| ++method_index; |
| } |
| size_t visited_virtuals = 0; |
| // TODO We should also check copied methods in this test. |
| for (auto& m : klass->GetDeclaredVirtualMethods(pointer_size)) { |
| if (!klass->IsInterface()) { |
| EXPECT_FALSE(m.IsCopied()); |
| } |
| CheckMethod(&m, oat_class.GetOatMethod(method_index), dex_file); |
| ++method_index; |
| ++visited_virtuals; |
| } |
| EXPECT_EQ(visited_virtuals, num_virtual_methods); |
| } |
| } |
| |
| TEST_F(OatTest, OatHeaderSizeCheck) { |
| // If this test is failing and you have to update these constants, |
| // it is time to update OatHeader::kOatVersion |
| EXPECT_EQ(64U, sizeof(OatHeader)); |
| EXPECT_EQ(4U, sizeof(OatMethodOffsets)); |
| EXPECT_EQ(4U, sizeof(OatQuickMethodHeader)); |
| EXPECT_EQ(168 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)), |
| sizeof(QuickEntryPoints)); |
| } |
| |
| TEST_F(OatTest, OatHeaderIsValid) { |
| InstructionSet insn_set = InstructionSet::kX86; |
| std::string error_msg; |
| std::unique_ptr<const InstructionSetFeatures> insn_features( |
| InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg)); |
| ASSERT_TRUE(insn_features.get() != nullptr) << error_msg; |
| std::unique_ptr<OatHeader> oat_header(OatHeader::Create(insn_set, |
| insn_features.get(), |
| 0u, |
| nullptr)); |
| ASSERT_NE(oat_header.get(), nullptr); |
| ASSERT_TRUE(oat_header->IsValid()); |
| |
| char* magic = const_cast<char*>(oat_header->GetMagic()); |
| strcpy(magic, ""); // bad magic |
| ASSERT_FALSE(oat_header->IsValid()); |
| strcpy(magic, "oat\n000"); // bad version |
| ASSERT_FALSE(oat_header->IsValid()); |
| } |
| |
| TEST_F(OatTest, EmptyTextSection) { |
| TimingLogger timings("OatTest::EmptyTextSection", false, false); |
| |
| std::vector<std::string> compiler_options; |
| compiler_options.push_back("--compiler-filter=extract"); |
| SetupCompiler(compiler_options); |
| |
| jobject class_loader; |
| { |
| ScopedObjectAccess soa(Thread::Current()); |
| class_loader = LoadDex("Main"); |
| } |
| ASSERT_TRUE(class_loader != nullptr); |
| std::vector<const DexFile*> dex_files = GetDexFiles(class_loader); |
| ASSERT_TRUE(!dex_files.empty()); |
| |
| ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); |
| for (const DexFile* dex_file : dex_files) { |
| ScopedObjectAccess soa(Thread::Current()); |
| class_linker->RegisterDexFile(*dex_file, soa.Decode<mirror::ClassLoader>(class_loader)); |
| } |
| CompileAll(class_loader, dex_files, &timings); |
| |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| SafeMap<std::string, std::string> key_value_store; |
| bool success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| dex_files, |
| key_value_store, |
| /*verify=*/ false); |
| ASSERT_TRUE(success); |
| |
| std::string error_msg; |
| std::unique_ptr<OatFile> oat_file(OatFile::Open(/*zip_fd=*/ -1, |
| tmp_oat.GetFilename(), |
| tmp_oat.GetFilename(), |
| /*executable=*/ false, |
| /*low_4gb=*/ false, |
| &error_msg)); |
| ASSERT_TRUE(oat_file != nullptr); |
| EXPECT_LT(static_cast<size_t>(oat_file->Size()), |
| static_cast<size_t>(tmp_oat.GetFile()->GetLength())); |
| } |
| |
| static void MaybeModifyDexFileToFail(bool verify, std::unique_ptr<const DexFile>& data) { |
| // If in verify mode (= fail the verifier mode), make sure we fail early. We'll fail already |
| // because of the missing map, but that may lead to out of bounds reads. |
| if (verify) { |
| const_cast<DexFile::Header*>(&data->GetHeader())->checksum_++; |
| } |
| } |
| |
| void OatTest::TestDexFileInput(bool verify, bool low_4gb, bool use_profile) { |
| TimingLogger timings("OatTest::DexFileInput", false, false); |
| |
| std::vector<const char*> input_filenames; |
| std::vector<std::unique_ptr<const DexFile>> input_dexfiles; |
| std::vector<const ScratchFile*> scratch_files; |
| |
| ScratchFile dex_file1; |
| TestDexFileBuilder builder1; |
| builder1.AddField("Lsome/TestClass;", "int", "someField"); |
| builder1.AddMethod("Lsome/TestClass;", "()I", "foo"); |
| std::unique_ptr<const DexFile> dex_file1_data = builder1.Build(dex_file1.GetFilename()); |
| |
| MaybeModifyDexFileToFail(verify, dex_file1_data); |
| |
| bool success = dex_file1.GetFile()->WriteFully(&dex_file1_data->GetHeader(), |
| dex_file1_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| success = dex_file1.GetFile()->Flush() == 0; |
| ASSERT_TRUE(success); |
| input_filenames.push_back(dex_file1.GetFilename().c_str()); |
| input_dexfiles.push_back(std::move(dex_file1_data)); |
| scratch_files.push_back(&dex_file1); |
| |
| ScratchFile dex_file2; |
| TestDexFileBuilder builder2; |
| builder2.AddField("Land/AnotherTestClass;", "boolean", "someOtherField"); |
| builder2.AddMethod("Land/AnotherTestClass;", "()J", "bar"); |
| std::unique_ptr<const DexFile> dex_file2_data = builder2.Build(dex_file2.GetFilename()); |
| |
| MaybeModifyDexFileToFail(verify, dex_file2_data); |
| |
| success = dex_file2.GetFile()->WriteFully(&dex_file2_data->GetHeader(), |
| dex_file2_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| success = dex_file2.GetFile()->Flush() == 0; |
| ASSERT_TRUE(success); |
| input_filenames.push_back(dex_file2.GetFilename().c_str()); |
| input_dexfiles.push_back(std::move(dex_file2_data)); |
| scratch_files.push_back(&dex_file2); |
| |
| SafeMap<std::string, std::string> key_value_store; |
| { |
| // Test using the AddDexFileSource() interface with the dex files. |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| std::unique_ptr<ProfileCompilationInfo> |
| profile_compilation_info(use_profile ? new ProfileCompilationInfo() : nullptr); |
| success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| input_filenames, |
| key_value_store, |
| verify, |
| CopyOption::kOnlyIfCompressed, |
| profile_compilation_info.get()); |
| |
| // In verify mode, we expect failure. |
| if (verify) { |
| ASSERT_FALSE(success); |
| return; |
| } |
| |
| ASSERT_TRUE(success); |
| |
| CheckOatWriteResult(tmp_oat, |
| tmp_vdex, |
| input_dexfiles, |
| /* oat_dexfile_count */ 2, |
| low_4gb); |
| } |
| |
| { |
| // Test using the AddDexFileSource() interface with the dexfile1's fd. |
| // Only need one input dexfile. |
| std::vector<std::unique_ptr<const DexFile>> input_dexfiles2; |
| input_dexfiles2.push_back(std::move(input_dexfiles[0])); |
| const ScratchFile* dex_file = scratch_files[0]; |
| File dex_file_fd(DupCloexec(dex_file->GetFd()), /*check_usage=*/ false); |
| |
| ASSERT_NE(-1, dex_file_fd.Fd()); |
| ASSERT_EQ(0, lseek(dex_file_fd.Fd(), 0, SEEK_SET)); |
| |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| std::unique_ptr<ProfileCompilationInfo> |
| profile_compilation_info(use_profile ? new ProfileCompilationInfo() : nullptr); |
| success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| std::move(dex_file_fd), |
| dex_file->GetFilename().c_str(), |
| key_value_store, |
| verify, |
| CopyOption::kOnlyIfCompressed, |
| profile_compilation_info.get()); |
| |
| // In verify mode, we expect failure. |
| if (verify) { |
| ASSERT_FALSE(success); |
| return; |
| } |
| |
| ASSERT_TRUE(success); |
| |
| CheckOatWriteResult(tmp_oat, |
| tmp_vdex, |
| input_dexfiles2, |
| /* oat_dexfile_count */ 1, |
| low_4gb); |
| } |
| } |
| |
| TEST_F(OatTest, DexFileInputCheckOutput) { |
| TestDexFileInput(/*verify*/false, /*low_4gb*/false, /*use_profile*/false); |
| } |
| |
| TEST_F(OatTest, DexFileInputCheckOutputLow4GB) { |
| TestDexFileInput(/*verify*/false, /*low_4gb*/true, /*use_profile*/false); |
| } |
| |
| TEST_F(OatTest, DexFileInputCheckVerifier) { |
| TestDexFileInput(/*verify*/true, /*low_4gb*/false, /*use_profile*/false); |
| } |
| |
| TEST_F(OatTest, DexFileFailsVerifierWithLayout) { |
| TestDexFileInput(/*verify*/true, /*low_4gb*/false, /*use_profile*/true); |
| } |
| |
| void OatTest::TestZipFileInput(bool verify, CopyOption copy) { |
| TimingLogger timings("OatTest::DexFileInput", false, false); |
| |
| ScratchFile zip_file; |
| ZipBuilder zip_builder(zip_file.GetFile()); |
| |
| ScratchFile dex_file1; |
| TestDexFileBuilder builder1; |
| builder1.AddField("Lsome/TestClass;", "long", "someField"); |
| builder1.AddMethod("Lsome/TestClass;", "()D", "foo"); |
| std::unique_ptr<const DexFile> dex_file1_data = builder1.Build(dex_file1.GetFilename()); |
| |
| MaybeModifyDexFileToFail(verify, dex_file1_data); |
| |
| bool success = dex_file1.GetFile()->WriteFully(&dex_file1_data->GetHeader(), |
| dex_file1_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| success = dex_file1.GetFile()->Flush() == 0; |
| ASSERT_TRUE(success); |
| success = zip_builder.AddFile("classes.dex", |
| &dex_file1_data->GetHeader(), |
| dex_file1_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| |
| ScratchFile dex_file2; |
| TestDexFileBuilder builder2; |
| builder2.AddField("Land/AnotherTestClass;", "boolean", "someOtherField"); |
| builder2.AddMethod("Land/AnotherTestClass;", "()J", "bar"); |
| std::unique_ptr<const DexFile> dex_file2_data = builder2.Build(dex_file2.GetFilename()); |
| |
| MaybeModifyDexFileToFail(verify, dex_file2_data); |
| |
| success = dex_file2.GetFile()->WriteFully(&dex_file2_data->GetHeader(), |
| dex_file2_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| success = dex_file2.GetFile()->Flush() == 0; |
| ASSERT_TRUE(success); |
| success = zip_builder.AddFile("classes2.dex", |
| &dex_file2_data->GetHeader(), |
| dex_file2_data->GetHeader().file_size_); |
| ASSERT_TRUE(success); |
| |
| success = zip_builder.Finish(); |
| ASSERT_TRUE(success) << strerror(errno); |
| |
| SafeMap<std::string, std::string> key_value_store; |
| { |
| // Test using the AddDexFileSource() interface with the zip file. |
| std::vector<const char*> input_filenames = { zip_file.GetFilename().c_str() }; |
| |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| input_filenames, |
| key_value_store, |
| verify, |
| copy, |
| /*profile_compilation_info=*/ nullptr); |
| |
| if (verify) { |
| ASSERT_FALSE(success); |
| } else { |
| ASSERT_TRUE(success); |
| |
| std::string error_msg; |
| std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/*zip_fd=*/ -1, |
| tmp_oat.GetFilename(), |
| tmp_oat.GetFilename(), |
| /*executable=*/ false, |
| /*low_4gb=*/ false, |
| &error_msg)); |
| ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; |
| ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); |
| std::unique_ptr<const DexFile> opened_dex_file1 = |
| opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); |
| std::unique_ptr<const DexFile> opened_dex_file2 = |
| opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); |
| |
| ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); |
| ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), |
| &opened_dex_file1->GetHeader(), |
| dex_file1_data->GetHeader().file_size_)); |
| ASSERT_EQ(DexFileLoader::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), |
| opened_dex_file1->GetLocation()); |
| |
| ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); |
| ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), |
| &opened_dex_file2->GetHeader(), |
| dex_file2_data->GetHeader().file_size_)); |
| ASSERT_EQ(DexFileLoader::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), |
| opened_dex_file2->GetLocation()); |
| } |
| } |
| |
| { |
| // Test using the AddDexFileSource() interface with the zip file handle. |
| File zip_fd(DupCloexec(zip_file.GetFd()), /*check_usage=*/ false); |
| ASSERT_NE(-1, zip_fd.Fd()); |
| ASSERT_EQ(0, lseek(zip_fd.Fd(), 0, SEEK_SET)); |
| |
| ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"), tmp_vdex(tmp_base, ".vdex"); |
| success = WriteElf(tmp_vdex.GetFile(), |
| tmp_oat.GetFile(), |
| std::move(zip_fd), |
| zip_file.GetFilename().c_str(), |
| key_value_store, |
| verify, |
| copy); |
| if (verify) { |
| ASSERT_FALSE(success); |
| } else { |
| ASSERT_TRUE(success); |
| |
| std::string error_msg; |
| std::unique_ptr<OatFile> opened_oat_file(OatFile::Open(/*zip_fd=*/ -1, |
| tmp_oat.GetFilename(), |
| tmp_oat.GetFilename(), |
| /*executable=*/ false, |
| /*low_4gb=*/ false, |
| &error_msg)); |
| ASSERT_TRUE(opened_oat_file != nullptr) << error_msg; |
| ASSERT_EQ(2u, opened_oat_file->GetOatDexFiles().size()); |
| std::unique_ptr<const DexFile> opened_dex_file1 = |
| opened_oat_file->GetOatDexFiles()[0]->OpenDexFile(&error_msg); |
| std::unique_ptr<const DexFile> opened_dex_file2 = |
| opened_oat_file->GetOatDexFiles()[1]->OpenDexFile(&error_msg); |
| |
| ASSERT_EQ(dex_file1_data->GetHeader().file_size_, opened_dex_file1->GetHeader().file_size_); |
| ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), |
| &opened_dex_file1->GetHeader(), |
| dex_file1_data->GetHeader().file_size_)); |
| ASSERT_EQ(DexFileLoader::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), |
| opened_dex_file1->GetLocation()); |
| |
| ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); |
| ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), |
| &opened_dex_file2->GetHeader(), |
| dex_file2_data->GetHeader().file_size_)); |
| ASSERT_EQ(DexFileLoader::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), |
| opened_dex_file2->GetLocation()); |
| } |
| } |
| } |
| |
| TEST_F(OatTest, ZipFileInputCheckOutput) { |
| TestZipFileInput(false, CopyOption::kOnlyIfCompressed); |
| } |
| |
| TEST_F(OatTest, ZipFileInputCheckOutputWithoutCopy) { |
| TestZipFileInput(false, CopyOption::kNever); |
| } |
| |
| TEST_F(OatTest, ZipFileInputCheckVerifier) { |
| TestZipFileInput(true, CopyOption::kOnlyIfCompressed); |
| } |
| |
| void OatTest::TestZipFileInputWithEmptyDex() { |
| ScratchFile zip_file; |
| ZipBuilder zip_builder(zip_file.GetFile()); |
| bool success = zip_builder.AddFile("classes.dex", nullptr, 0); |
| ASSERT_TRUE(success); |
| success = zip_builder.Finish(); |
| ASSERT_TRUE(success) << strerror(errno); |
| |
| SafeMap<std::string, std::string> key_value_store; |
| std::vector<const char*> input_filenames = { zip_file.GetFilename().c_str() }; |
| ScratchFile oat_file, vdex_file(oat_file, ".vdex"); |
| std::unique_ptr<ProfileCompilationInfo> profile_compilation_info(new ProfileCompilationInfo()); |
| success = WriteElf(vdex_file.GetFile(), |
| oat_file.GetFile(), |
| input_filenames, |
| key_value_store, |
| /*verify=*/ false, |
| CopyOption::kOnlyIfCompressed, |
| profile_compilation_info.get()); |
| ASSERT_FALSE(success); |
| } |
| |
| TEST_F(OatTest, ZipFileInputWithEmptyDex) { |
| TestZipFileInputWithEmptyDex(); |
| } |
| |
| } // namespace linker |
| } // namespace art |