diff options
-rw-r--r-- | build/Android.common_test.mk | 10 | ||||
-rw-r--r-- | build/Android.gtest.mk | 32 | ||||
-rw-r--r-- | cmdline/cmdline_parser_test.cc | 7 | ||||
-rw-r--r-- | cmdline/cmdline_types.h | 2 | ||||
-rw-r--r-- | runtime/Android.mk | 1 | ||||
-rw-r--r-- | runtime/base/logging.h | 1 | ||||
-rw-r--r-- | runtime/class_linker.cc | 928 | ||||
-rw-r--r-- | runtime/class_linker.h | 107 | ||||
-rw-r--r-- | runtime/common_runtime_test.cc | 11 | ||||
-rw-r--r-- | runtime/common_runtime_test.h | 8 | ||||
-rw-r--r-- | runtime/native/dalvik_system_DexFile.cc | 360 | ||||
-rw-r--r-- | runtime/oat_file_assistant.cc | 952 | ||||
-rw-r--r-- | runtime/oat_file_assistant.h | 431 | ||||
-rw-r--r-- | runtime/oat_file_assistant_test.cc | 874 | ||||
-rw-r--r-- | runtime/utils.cc | 17 | ||||
-rw-r--r-- | runtime/utils.h | 6 | ||||
-rw-r--r-- | runtime/utils_test.cc | 7 | ||||
-rw-r--r-- | test/MultiDex/Main.java | 22 | ||||
-rw-r--r-- | test/MultiDex/Second.java | 21 | ||||
-rw-r--r-- | test/MultiDex/main.list | 1 |
20 files changed, 2471 insertions, 1327 deletions
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk index b536fe4a4d..c750399387 100644 --- a/build/Android.common_test.mk +++ b/build/Android.common_test.mk @@ -160,6 +160,10 @@ endef # $(4): additional dependencies # $(5): a make variable used to collate target dependencies, e.g ART_TEST_TARGET_OAT_HelloWorld_DEX # $(6): a make variable used to collate host dependencies, e.g ART_TEST_HOST_OAT_HelloWorld_DEX +# +# If the input test directory contains a file called main.list, then a +# multi-dex file is created passing main.list as the --main-dex-list argument +# to dx. define build-art-test-dex ifeq ($(ART_BUILD_TARGET),true) include $(CLEAR_VARS) @@ -172,6 +176,9 @@ define build-art-test-dex LOCAL_JAVA_LIBRARIES := $(TARGET_CORE_JARS) LOCAL_MODULE_PATH := $(3) LOCAL_DEX_PREOPT_IMAGE_LOCATION := $(TARGET_CORE_IMG_OUT) + ifneq ($(wildcard $(LOCAL_PATH)/$(2)/main.list),) + LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(LOCAL_PATH)/$(2)/main.list --minimal-main-dex + endif include $(BUILD_JAVA_LIBRARY) $(5) := $$(LOCAL_INSTALLED_MODULE) endif @@ -184,6 +191,9 @@ define build-art-test-dex LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_test.mk $(4) LOCAL_JAVA_LIBRARIES := $(HOST_CORE_JARS) LOCAL_DEX_PREOPT_IMAGE := $(HOST_CORE_IMG_LOCATION) + ifneq ($(wildcard $(LOCAL_PATH)/$(2)/main.list),) + LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(LOCAL_PATH)/$(2)/main.list --minimal-main-dex + endif include $(BUILD_HOST_DALVIK_JAVA_LIBRARY) $(6) := $$(LOCAL_INSTALLED_MODULE) endif diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 7ab4d64d45..6967808b8e 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -28,6 +28,7 @@ GTEST_DEX_DIRECTORIES := \ GetMethodSignature \ Interfaces \ Main \ + MultiDex \ MyClass \ MyClassNatives \ Nested \ @@ -45,6 +46,19 @@ $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval $(call build-art-test-dex,art-gte $(ART_TARGET_NATIVETEST_OUT),art/build/Android.gtest.mk,ART_TEST_TARGET_GTEST_$(dir)_DEX, \ ART_TEST_HOST_GTEST_$(dir)_DEX))) +# Create rules for MainStripped, a copy of Main with the classes.dex stripped +# for the oat file assistant tests. +ART_TEST_HOST_GTEST_MainStripped_DEX := $(basename $(ART_TEST_HOST_GTEST_Main_DEX))Stripped$(suffix $(ART_TEST_HOST_GTEST_Main_DEX)) +ART_TEST_TARGET_GTEST_MainStripped_DEX := $(basename $(ART_TEST_TARGET_GTEST_Main_DEX))Stripped$(suffix $(ART_TEST_TARGET_GTEST_Main_DEX)) + +$(ART_TEST_HOST_GTEST_MainStripped_DEX): $(ART_TEST_HOST_GTEST_Main_DEX) + cp $< $@ + $(call dexpreopt-remove-classes.dex,$@) + +$(ART_TEST_TARGET_GTEST_MainStripped_DEX): $(ART_TEST_TARGET_GTEST_Main_DEX) + cp $< $@ + $(call dexpreopt-remove-classes.dex,$@) + # Dex file dependencies for each gtest. ART_GTEST_class_linker_test_DEX_DEPS := Interfaces MyClass Nested Statics StaticsFromCode ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod @@ -52,6 +66,7 @@ ART_GTEST_dex_file_test_DEX_DEPS := GetMethodSignature Main Nested ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods +ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex Nested ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods @@ -62,6 +77,9 @@ ART_GTEST_transaction_test_DEX_DEPS := Transaction ART_GTEST_elf_writer_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_default_no-pic_64) $(TARGET_CORE_IMAGE_default_no-pic_32) +ART_GTEST_oat_file_assistant_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) +ART_GTEST_oat_file_assistant_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_default_no-pic_64) $(TARGET_CORE_IMAGE_default_no-pic_32) + # TODO: document why this is needed. ART_GTEST_proxy_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) @@ -141,6 +159,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/mirror/object_test.cc \ runtime/monitor_pool_test.cc \ runtime/monitor_test.cc \ + runtime/oat_file_assistant_test.cc \ runtime/parsed_options_test.cc \ runtime/reference_table_test.cc \ runtime/thread_pool_test.cc \ @@ -462,12 +481,12 @@ valgrind-test-art-host-gtest-$$(art_gtest_name): $$(ART_TEST_HOST_VALGRIND_GTEST endef # define-art-gtest ifeq ($(ART_BUILD_TARGET),true) - $(foreach file,$(RUNTIME_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),,))) - $(foreach file,$(COMPILER_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),art/compiler,libartd-compiler))) + $(foreach file,$(RUNTIME_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),,libbacktrace))) + $(foreach file,$(COMPILER_GTEST_TARGET_SRC_FILES), $(eval $(call define-art-gtest,target,$(file),art/compiler,libartd-compiler libbacktrace))) endif ifeq ($(ART_BUILD_HOST),true) - $(foreach file,$(RUNTIME_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),,))) - $(foreach file,$(COMPILER_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),art/compiler,libartd-compiler))) + $(foreach file,$(RUNTIME_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),,libbacktrace))) + $(foreach file,$(COMPILER_GTEST_HOST_SRC_FILES), $(eval $(call define-art-gtest,host,$(file),art/compiler,libartd-compiler libbacktrace))) endif # Used outside the art project to get a list of the current tests @@ -559,6 +578,9 @@ ART_GTEST_elf_writer_test_HOST_DEPS := ART_GTEST_elf_writer_test_TARGET_DEPS := ART_GTEST_jni_compiler_test_DEX_DEPS := ART_GTEST_jni_internal_test_DEX_DEPS := +ART_GTEST_oat_file_assistant_test_DEX_DEPS := +ART_GTEST_oat_file_assistant_test_HOST_DEPS := +ART_GTEST_oat_file_assistant_test_TARGET_DEPS := ART_GTEST_object_test_DEX_DEPS := ART_GTEST_proxy_test_DEX_DEPS := ART_GTEST_reflection_test_DEX_DEPS := @@ -567,5 +589,7 @@ ART_GTEST_transaction_test_DEX_DEPS := ART_VALGRIND_DEPENDENCIES := $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_TARGET_GTEST_$(dir)_DEX :=)) $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_HOST_GTEST_$(dir)_DEX :=)) +ART_TEST_HOST_GTEST_MainStripped_DEX := +ART_TEST_TARGET_GTEST_MainStripped_DEX := GTEST_DEX_DIRECTORIES := LOCAL_PATH := diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index 130eed2807..9f873b321a 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -260,6 +260,13 @@ TEST_F(CmdlineParserTest, TestLogVerbosity) { } EXPECT_SINGLE_PARSE_FAIL("-verbose:blablabla", CmdlineResult::kUsage); // invalid verbose opt + + { + const char* log_args = "-verbose:oat"; + LogVerbosity log_verbosity = LogVerbosity(); + log_verbosity.oat = true; + EXPECT_SINGLE_PARSE_VALUE(log_verbosity, log_args, M::Verbose); + } } // TEST_F // TODO: Enable this b/19274810 diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index de99278389..03165ed5a6 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -591,6 +591,8 @@ struct CmdlineType<LogVerbosity> : CmdlineTypeParser<LogVerbosity> { log_verbosity.jni = true; } else if (verbose_options[j] == "monitor") { log_verbosity.monitor = true; + } else if (verbose_options[j] == "oat") { + log_verbosity.oat = true; } else if (verbose_options[j] == "profiler") { log_verbosity.profiler = true; } else if (verbose_options[j] == "signals") { diff --git a/runtime/Android.mk b/runtime/Android.mk index c5cf89014e..6490434c96 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -135,6 +135,7 @@ LIBART_COMMON_SRC_FILES := \ native/sun_misc_Unsafe.cc \ oat.cc \ oat_file.cc \ + oat_file_assistant.cc \ object_lock.cc \ offsets.cc \ os_linux.cc \ diff --git a/runtime/base/logging.h b/runtime/base/logging.h index 3d007ba1e5..014f4ab5bf 100644 --- a/runtime/base/logging.h +++ b/runtime/base/logging.h @@ -45,6 +45,7 @@ struct LogVerbosity { bool jit; bool jni; bool monitor; + bool oat; bool profiler; bool signals; bool startup; diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index f0c8819a52..785adf5e7e 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -48,6 +48,7 @@ #include "leb128.h" #include "oat.h" #include "oat_file.h" +#include "oat_file_assistant.h" #include "object_lock.h" #include "mirror/art_field-inl.h" #include "mirror/art_method-inl.h" @@ -662,77 +663,6 @@ void ClassLinker::RunRootClinits() { } } -bool ClassLinker::GenerateOatFile(const char* dex_filename, - int oat_fd, - const char* oat_cache_filename, - std::string* error_msg) { - Locks::mutator_lock_->AssertNotHeld(Thread::Current()); // Avoid starving GC. - std::string dex2oat(Runtime::Current()->GetCompilerExecutable()); - - gc::Heap* heap = Runtime::Current()->GetHeap(); - std::string boot_image_option("--boot-image="); - if (heap->GetImageSpace() == nullptr) { - // TODO If we get a dex2dex compiler working we could maybe use that, OTOH since we are likely - // out of space anyway it might not matter. - *error_msg = StringPrintf("Cannot create oat file for '%s' because we are running " - "without an image.", dex_filename); - return false; - } - boot_image_option += heap->GetImageSpace()->GetImageLocation(); - - std::string dex_file_option("--dex-file="); - dex_file_option += dex_filename; - - std::string oat_fd_option("--oat-fd="); - StringAppendF(&oat_fd_option, "%d", oat_fd); - - std::string oat_location_option("--oat-location="); - oat_location_option += oat_cache_filename; - - std::vector<std::string> argv; - argv.push_back(dex2oat); - argv.push_back("--runtime-arg"); - argv.push_back("-classpath"); - argv.push_back("--runtime-arg"); - argv.push_back(Runtime::Current()->GetClassPathString()); - - Runtime::Current()->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); - - if (!Runtime::Current()->IsVerificationEnabled()) { - argv.push_back("--compiler-filter=verify-none"); - } - - if (Runtime::Current()->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_option); - argv.push_back(dex_file_option); - argv.push_back(oat_fd_option); - argv.push_back(oat_location_option); - const std::vector<std::string>& compiler_options = Runtime::Current()->GetCompilerOptions(); - for (size_t i = 0; i < compiler_options.size(); ++i) { - argv.push_back(compiler_options[i].c_str()); - } - - if (!Exec(argv, error_msg)) { - // Manually delete the file. Ensures there is no garbage left over if the process unexpectedly - // died. Ignore unlink failure, propagate the original error. - TEMP_FAILURE_RETRY(unlink(oat_cache_filename)); - return false; - } - - return true; -} - const OatFile* ClassLinker::RegisterOatFile(const OatFile* oat_file) { WriterMutexLock mu(Thread::Current(), dex_lock_); if (kIsDebugBuild) { @@ -782,504 +712,81 @@ const OatFile::OatDexFile* ClassLinker::FindOpenedOatDexFile(const char* oat_loc return nullptr; } +std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat( + const char* dex_location, const char* oat_location, + std::vector<std::string>* error_msgs) { + CHECK(error_msgs != nullptr); -// Loads all multi dex files from the given oat file returning true on success. -// -// Parameters: -// oat_file - the oat file to load from -// dex_location - the dex location used to generate the oat file -// dex_location_checksum - the checksum of the dex_location (may be null for pre-opted files) -// generated - whether or not the oat_file existed before or was just (re)generated -// error_msgs - any error messages will be appended here -// dex_files - the loaded dex_files will be appended here (only if the loading succeeds) -static bool LoadMultiDexFilesFromOatFile(const OatFile* oat_file, - const char* dex_location, - const uint32_t* dex_location_checksum, - bool generated, - std::vector<std::string>* error_msgs, - std::vector<std::unique_ptr<const DexFile>>* dex_files) { - if (oat_file == nullptr) { - return false; - } - - size_t old_size = dex_files->size(); // To rollback on error. - - bool success = true; - for (size_t i = 0; success; ++i) { - std::string next_name_str = DexFile::GetMultiDexClassesDexName(i, dex_location); - const char* next_name = next_name_str.c_str(); - - uint32_t next_location_checksum; - uint32_t* next_location_checksum_pointer = &next_location_checksum; - std::string error_msg; - if ((i == 0) && (strcmp(next_name, dex_location) == 0)) { - // When i=0 the multidex name should be the same as the location name. We already have the - // checksum it so we don't need to recompute it. - if (dex_location_checksum == nullptr) { - next_location_checksum_pointer = nullptr; - } else { - next_location_checksum = *dex_location_checksum; - } - } else if (!DexFile::GetChecksum(next_name, next_location_checksum_pointer, &error_msg)) { - DCHECK_EQ(false, i == 0 && generated); - next_location_checksum_pointer = nullptr; - } + // Verify we aren't holding the mutator lock, which could starve GC if we + // have to generate or relocate an oat file. + Locks::mutator_lock_->AssertNotHeld(Thread::Current()); - const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(next_name, nullptr, false); + OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA, + !Runtime::Current()->IsAotCompiler()); - if (oat_dex_file == nullptr) { - if (i == 0 && generated) { - error_msg = StringPrintf("\nFailed to find dex file '%s' (checksum 0x%x) in generated out " - " file'%s'", dex_location, next_location_checksum, - oat_file->GetLocation().c_str()); - error_msgs->push_back(error_msg); - } - break; // Not found, done. - } - - // Checksum test. Test must succeed when generated. - success = !generated; - if (next_location_checksum_pointer != nullptr) { - success = next_location_checksum == oat_dex_file->GetDexFileLocationChecksum(); - } - - if (success) { - std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg); - if (dex_file.get() == nullptr) { - success = false; - error_msgs->push_back(error_msg); - } else { - dex_files->push_back(std::move(dex_file)); - } - } - - // When we generated the file, we expect success, or something is terribly wrong. - CHECK_EQ(false, generated && !success) - << "dex_location=" << next_name << " oat_location=" << oat_file->GetLocation().c_str() - << std::hex << " dex_location_checksum=" << next_location_checksum - << " OatDexFile::GetLocationChecksum()=" << oat_dex_file->GetDexFileLocationChecksum(); - } - - if (dex_files->size() == old_size) { - success = false; // We did not even find classes.dex - } - - if (success) { - return true; - } else { - dex_files->erase(dex_files->begin() + old_size, dex_files->end()); - return false; + // Lock the target oat location to avoid races generating and loading the + // oat file. + std::string error_msg; + if (!oat_file_assistant.Lock(&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; } -} - -// Multidex files make it possible that some, but not all, dex files can be broken/outdated. This -// complicates the loading process, as we should not use an iterative loading process, because that -// would register the oat file and dex files that come before the broken one. Instead, check all -// multidex ahead of time. -bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location, - std::vector<std::string>* error_msgs, - std::vector<std::unique_ptr<const DexFile>>* dex_files) { - // 1) Check whether we have an open oat file. - // This requires a dex checksum, use the "primary" one. - uint32_t dex_location_checksum; - uint32_t* dex_location_checksum_pointer = &dex_location_checksum; - bool have_checksum = true; - std::string checksum_error_msg; - if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) { - // This happens for pre-opted files since the corresponding dex files are no longer on disk. - dex_location_checksum_pointer = nullptr; - have_checksum = false; - } - - bool needs_registering = false; - - const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, - dex_location_checksum_pointer); - std::unique_ptr<const OatFile> open_oat_file( - oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr); - - // 2) If we do not have an open one, maybe there's one on disk already. - - // In case the oat file is not open, we play a locking game here so - // that if two different processes race to load and register or generate - // (or worse, one tries to open a partial generated file) we will be okay. - // This is actually common with apps that use DexClassLoader to work - // around the dex method reference limit and that have a background - // service running in a separate process. - ScopedFlock scoped_flock; - - if (open_oat_file.get() == nullptr) { - if (oat_location != nullptr) { - // Can only do this if we have a checksum, else error. - if (!have_checksum) { - error_msgs->push_back(checksum_error_msg); - return false; - } - std::string error_msg; - - // We are loading or creating one in the future. Time to set up the file lock. - if (!scoped_flock.Init(oat_location, &error_msg)) { - error_msgs->push_back(error_msg); - return false; - } - - // TODO Caller specifically asks for this oat_location. We should honor it. Probably? - open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum, - oat_location, &error_msg)); - - if (open_oat_file.get() == nullptr) { - std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s", - dex_location, oat_location, error_msg.c_str()); - VLOG(class_linker) << compound_msg; - error_msgs->push_back(compound_msg); - } - } else { - // TODO: What to lock here? - bool obsolete_file_cleanup_failed; - open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location, - dex_location_checksum_pointer, - kRuntimeISA, error_msgs, - &obsolete_file_cleanup_failed)); - // There's no point in going forward and eventually try to regenerate the - // file if we couldn't remove the obsolete one. Mostly likely we will fail - // with the same error when trying to write the new file. - // TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS). - if (obsolete_file_cleanup_failed) { - return false; + // Check if we already have an up-to-date oat file open. + const OatFile* source_oat_file = nullptr; + { + ReaderMutexLock mu(Thread::Current(), dex_lock_); + for (const OatFile* oat_file : oat_files_) { + CHECK(oat_file != nullptr); + if (oat_file_assistant.GivenOatFileIsUpToDate(*oat_file)) { + source_oat_file = oat_file; + break; } } - needs_registering = true; } - // 3) If we have an oat file, check all contained multidex files for our dex_location. - // Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument. - bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, - dex_location_checksum_pointer, - false, error_msgs, dex_files); - if (success) { - const OatFile* oat_file = open_oat_file.release(); // Avoid deleting it. - if (needs_registering) { - // We opened the oat file, so we must register it. - RegisterOatFile(oat_file); + // If we didn't have an up-to-date oat file open, try to load one from disk. + if (source_oat_file == nullptr) { + // Update the oat file on disk if we can. This may fail, but that's okay. + // Best effort is all that matters here. + if (!oat_file_assistant.MakeUpToDate(&error_msg)) { + LOG(WARNING) << error_msg; } - // If the file isn't executable we failed patchoat but did manage to get the dex files. - return oat_file->IsExecutable(); - } else { - if (needs_registering) { - // We opened it, delete it. - open_oat_file.reset(); - } else { - open_oat_file.release(); // Do not delete open oat files. - } - } - - // 4) If it's not the case (either no oat file or mismatches), regenerate and load. - // Need a checksum, fail else. - if (!have_checksum) { - error_msgs->push_back(checksum_error_msg); - return false; + // Get the oat file on disk. + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + if (oat_file.get() != nullptr) { + source_oat_file = oat_file.release(); + RegisterOatFile(source_oat_file); + } } - // Look in cache location if no oat_location is given. - std::string cache_location; - if (oat_location == nullptr) { - // Use the dalvik cache. - const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA))); - cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str()); - oat_location = cache_location.c_str(); - } + std::vector<std::unique_ptr<const DexFile>> dex_files; - bool has_flock = true; - // Definitely need to lock now. - if (!scoped_flock.HasFile()) { - std::string error_msg; - if (!scoped_flock.Init(oat_location, &error_msg)) { - error_msgs->push_back(error_msg); - has_flock = false; + // Load the dex files from the oat file. + if (source_oat_file != nullptr) { + dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location); + if (dex_files.empty()) { + error_msgs->push_back("Failed to open dex files from " + + source_oat_file->GetLocation()); } } - if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) { - // Create the oat file. - open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(), - oat_location, error_msgs)); - } - - // Failed, bail. - if (open_oat_file.get() == nullptr) { - // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress. + // Fall back to running out of the original dex file if we couldn't load any + // dex_files from the oat file. + if (dex_files.empty()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { - std::string error_msg; - if (!DexFile::Open(dex_location, dex_location, &error_msg, dex_files)) { - error_msgs->push_back(error_msg); + if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) { + LOG(WARNING) << error_msg; + error_msgs->push_back("Failed to open dex files from " + + std::string(dex_location)); } } else { error_msgs->push_back("Fallback mode disabled, skipping dex files."); } - return false; - } - - // Try to load again, but stronger checks. - success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, - dex_location_checksum_pointer, - true, error_msgs, dex_files); - if (success) { - RegisterOatFile(open_oat_file.release()); - return true; - } else { - return false; - } -} - -const OatFile* ClassLinker::FindOatFileInOatLocationForDexFile(const char* dex_location, - uint32_t dex_location_checksum, - const char* oat_location, - std::string* error_msg) { - std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, nullptr, nullptr, - !Runtime::Current()->IsAotCompiler(), error_msg)); - if (oat_file.get() == nullptr) { - *error_msg = StringPrintf("Failed to find existing oat file at %s: %s", oat_location, - error_msg->c_str()); - return nullptr; - } - Runtime* runtime = Runtime::Current(); - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); - if (image_space != nullptr) { - const ImageHeader& image_header = image_space->GetImageHeader(); - uint32_t expected_image_oat_checksum = image_header.GetOatChecksum(); - uint32_t actual_image_oat_checksum = oat_file->GetOatHeader().GetImageFileLocationOatChecksum(); - if (expected_image_oat_checksum != actual_image_oat_checksum) { - *error_msg = StringPrintf("Failed to find oat file at '%s' with expected image oat checksum of " - "0x%x, found 0x%x", oat_location, expected_image_oat_checksum, - actual_image_oat_checksum); - return nullptr; - } - - uintptr_t expected_image_oat_offset = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()); - uint32_t actual_image_oat_offset = oat_file->GetOatHeader().GetImageFileLocationOatDataBegin(); - if (expected_image_oat_offset != actual_image_oat_offset) { - *error_msg = StringPrintf("Failed to find oat file at '%s' with expected image oat offset %" - PRIuPTR ", found %ud", oat_location, expected_image_oat_offset, - actual_image_oat_offset); - return nullptr; - } - int32_t expected_patch_delta = image_header.GetPatchDelta(); - int32_t actual_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta(); - if (expected_patch_delta != actual_patch_delta) { - *error_msg = StringPrintf("Failed to find oat file at '%s' with expected patch delta %d, " - " found %d", oat_location, expected_patch_delta, actual_patch_delta); - return nullptr; - } - } - - const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, - &dex_location_checksum); - if (oat_dex_file == nullptr) { - *error_msg = StringPrintf("Failed to find oat file at '%s' containing '%s'", oat_location, - dex_location); - return nullptr; - } - uint32_t expected_dex_checksum = dex_location_checksum; - uint32_t actual_dex_checksum = oat_dex_file->GetDexFileLocationChecksum(); - if (expected_dex_checksum != actual_dex_checksum) { - *error_msg = StringPrintf("Failed to find oat file at '%s' with expected dex checksum of 0x%x, " - "found 0x%x", oat_location, expected_dex_checksum, - actual_dex_checksum); - return nullptr; - } - std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(error_msg)); - if (dex_file.get() != nullptr) { - return oat_file.release(); - } else { - return nullptr; - } -} - -const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location, - int fd, const char* oat_location, - std::vector<std::string>* error_msgs) { - // Generate the output oat file for the dex file - VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location; - std::string error_msg; - if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) { - CHECK(!error_msg.empty()); - error_msgs->push_back(error_msg); - return nullptr; - } - std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, nullptr, nullptr, - !Runtime::Current()->IsAotCompiler(), - &error_msg)); - if (oat_file.get() == nullptr) { - std::string compound_msg = StringPrintf("\nFailed to open generated oat file '%s': %s", - oat_location, error_msg.c_str()); - error_msgs->push_back(compound_msg); - return nullptr; - } - - return oat_file.release(); -} - -bool ClassLinker::VerifyOatImageChecksum(const OatFile* oat_file, - const InstructionSet instruction_set) { - Runtime* runtime = Runtime::Current(); - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); - if (image_space == nullptr) { - return false; - } - uint32_t image_oat_checksum = 0; - if (instruction_set == kRuntimeISA) { - const ImageHeader& image_header = image_space->GetImageHeader(); - image_oat_checksum = image_header.GetOatChecksum(); - } else { - std::unique_ptr<ImageHeader> image_header(gc::space::ImageSpace::ReadImageHeaderOrDie( - image_space->GetImageLocation().c_str(), instruction_set)); - image_oat_checksum = image_header->GetOatChecksum(); - } - return oat_file->GetOatHeader().GetImageFileLocationOatChecksum() == image_oat_checksum; -} - -bool ClassLinker::VerifyOatChecksums(const OatFile* oat_file, - const InstructionSet instruction_set, - std::string* error_msg) { - Runtime* runtime = Runtime::Current(); - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); - if (image_space == nullptr) { - *error_msg = "No image space for verification against"; - return false; - } - - // If the requested instruction set is the same as the current runtime, - // we can use the checksums directly. If it isn't, we'll have to read the - // image header from the image for the right instruction set. - uint32_t image_oat_checksum = 0; - uintptr_t image_oat_data_begin = 0; - int32_t image_patch_delta = 0; - if (instruction_set == runtime->GetInstructionSet()) { - const ImageHeader& image_header = image_space->GetImageHeader(); - image_oat_checksum = image_header.GetOatChecksum(); - image_oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()); - image_patch_delta = image_header.GetPatchDelta(); - } else { - std::unique_ptr<ImageHeader> image_header(gc::space::ImageSpace::ReadImageHeaderOrDie( - image_space->GetImageLocation().c_str(), instruction_set)); - image_oat_checksum = image_header->GetOatChecksum(); - image_oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()); - image_patch_delta = image_header->GetPatchDelta(); - } - const OatHeader& oat_header = oat_file->GetOatHeader(); - bool ret = (oat_header.GetImageFileLocationOatChecksum() == image_oat_checksum); - - // If the oat file is PIC, it doesn't care if/how image was relocated. Ignore these checks. - if (!oat_file->IsPic()) { - ret = ret && (oat_header.GetImagePatchDelta() == image_patch_delta) - && (oat_header.GetImageFileLocationOatDataBegin() == image_oat_data_begin); - } - if (!ret) { - *error_msg = StringPrintf("oat file '%s' mismatch (0x%x, %d, %d) with (0x%x, %" PRIdPTR ", %d)", - oat_file->GetLocation().c_str(), - oat_file->GetOatHeader().GetImageFileLocationOatChecksum(), - oat_file->GetOatHeader().GetImageFileLocationOatDataBegin(), - oat_file->GetOatHeader().GetImagePatchDelta(), - image_oat_checksum, image_oat_data_begin, image_patch_delta); - } - return ret; -} - -bool ClassLinker::VerifyOatAndDexFileChecksums(const OatFile* oat_file, - const char* dex_location, - uint32_t dex_location_checksum, - const InstructionSet instruction_set, - std::string* error_msg) { - if (!VerifyOatChecksums(oat_file, instruction_set, error_msg)) { - return false; - } - - const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, - &dex_location_checksum); - if (oat_dex_file == nullptr) { - *error_msg = StringPrintf("oat file '%s' does not contain contents for '%s' with checksum 0x%x", - oat_file->GetLocation().c_str(), dex_location, dex_location_checksum); - for (const OatFile::OatDexFile* oat_dex_file_in : oat_file->GetOatDexFiles()) { - *error_msg += StringPrintf("\noat file '%s' contains contents for '%s' with checksum 0x%x", - oat_file->GetLocation().c_str(), - oat_dex_file_in->GetDexFileLocation().c_str(), - oat_dex_file_in->GetDexFileLocationChecksum()); - } - return false; - } - - DCHECK_EQ(dex_location_checksum, oat_dex_file->GetDexFileLocationChecksum()); - return true; -} - -bool ClassLinker::VerifyOatWithDexFile(const OatFile* oat_file, - const char* dex_location, - const uint32_t* dex_location_checksum, - std::string* error_msg) { - CHECK(oat_file != nullptr); - CHECK(dex_location != nullptr); - std::unique_ptr<const DexFile> dex_file; - if (dex_location_checksum == nullptr) { - // If no classes.dex found in dex_location, it has been stripped or is corrupt, assume oat is - // up-to-date. This is the common case in user builds for jar's and apk's in the /system - // directory. - const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, nullptr); - if (oat_dex_file == nullptr) { - *error_msg = StringPrintf("Dex checksum mismatch for location '%s' and failed to find oat " - "dex file '%s': %s", oat_file->GetLocation().c_str(), dex_location, - error_msg->c_str()); - return false; - } - dex_file = oat_dex_file->OpenDexFile(error_msg); - } else { - bool verified = VerifyOatAndDexFileChecksums(oat_file, dex_location, *dex_location_checksum, - kRuntimeISA, error_msg); - if (!verified) { - return false; - } - dex_file = oat_file->GetOatDexFile(dex_location, - dex_location_checksum)->OpenDexFile(error_msg); - } - return dex_file.get() != nullptr; -} - -const OatFile* ClassLinker::FindOatFileContainingDexFileFromDexLocation( - const char* dex_location, - const uint32_t* dex_location_checksum, - InstructionSet isa, - std::vector<std::string>* error_msgs, - bool* obsolete_file_cleanup_failed) { - *obsolete_file_cleanup_failed = false; - bool already_opened = false; - std::string dex_location_str(dex_location); - std::unique_ptr<const OatFile> oat_file(OpenOatFileFromDexLocation(dex_location_str, isa, - &already_opened, - obsolete_file_cleanup_failed, - error_msgs)); - std::string error_msg; - if (oat_file.get() == nullptr) { - error_msgs->push_back(StringPrintf("Failed to open oat file from dex location '%s'", - dex_location)); - return nullptr; - } else if (oat_file->IsExecutable() && - !VerifyOatWithDexFile(oat_file.get(), dex_location, - dex_location_checksum, &error_msg)) { - error_msgs->push_back(StringPrintf("Failed to verify oat file '%s' found for dex location " - "'%s': %s", oat_file->GetLocation().c_str(), dex_location, - error_msg.c_str())); - return nullptr; - } else if (!oat_file->IsExecutable() && - Runtime::Current()->GetHeap()->HasImageSpace() && - !VerifyOatImageChecksum(oat_file.get(), isa)) { - error_msgs->push_back(StringPrintf("Failed to verify non-executable oat file '%s' found for " - "dex location '%s'. Image checksum incorrect.", - oat_file->GetLocation().c_str(), dex_location)); - return nullptr; - } else { - return oat_file.release(); } + return dex_files; } const OatFile* ClassLinker::FindOpenedOatFileFromOatLocation(const std::string& oat_location) { @@ -1294,335 +801,6 @@ const OatFile* ClassLinker::FindOpenedOatFileFromOatLocation(const std::string& return nullptr; } -const OatFile* ClassLinker::OpenOatFileFromDexLocation(const std::string& dex_location, - InstructionSet isa, - bool *already_opened, - bool *obsolete_file_cleanup_failed, - std::vector<std::string>* error_msgs) { - // Find out if we've already opened the file - const OatFile* ret = nullptr; - std::string odex_filename(DexFilenameToOdexFilename(dex_location, isa)); - ret = FindOpenedOatFileFromOatLocation(odex_filename); - if (ret != nullptr) { - *already_opened = true; - return ret; - } - - std::string dalvik_cache; - bool have_android_data = false; - bool have_dalvik_cache = false; - bool is_global_cache = false; - GetDalvikCache(GetInstructionSetString(kRuntimeISA), false, &dalvik_cache, - &have_android_data, &have_dalvik_cache, &is_global_cache); - std::string cache_filename; - if (have_dalvik_cache) { - cache_filename = GetDalvikCacheFilenameOrDie(dex_location.c_str(), dalvik_cache.c_str()); - ret = FindOpenedOatFileFromOatLocation(cache_filename); - if (ret != nullptr) { - *already_opened = true; - return ret; - } - } else { - // If we need to relocate we should just place odex back where it started. - cache_filename = odex_filename; - } - - ret = nullptr; - - // We know that neither the odex nor the cache'd version is already in use, if it even exists. - // - // Now we do the following: - // 1) Try and open the odex version - // 2) If present, checksum-verified & relocated correctly return it - // 3) Close the odex version to free up its address space. - // 4) Try and open the cache version - // 5) If present, checksum-verified & relocated correctly return it - // 6) Close the cache version to free up its address space. - // 7) If we should relocate: - // a) If we have opened and checksum-verified the odex version relocate it to - // 'cache_filename' and return it - // b) If we have opened and checksum-verified the cache version relocate it in place and return - // it. This should not happen often (I think only the run-test's will hit this case). - // 8) If the cache-version was present we should delete it since it must be obsolete if we get to - // this point. - // 9) Return nullptr - - *already_opened = false; - const Runtime* runtime = Runtime::Current(); - CHECK(runtime != nullptr); - bool executable = !runtime->IsAotCompiler(); - - std::string odex_error_msg; - bool should_patch_system = false; - bool odex_checksum_verified = false; - bool have_system_odex = false; - { - // There is a high probability that both these oat files map similar/the same address - // spaces so we must scope them like this so they each gets its turn. - std::unique_ptr<OatFile> odex_oat_file(OatFile::Open(odex_filename, odex_filename, nullptr, - nullptr, - executable, &odex_error_msg)); - if (odex_oat_file.get() != nullptr && CheckOatFile(runtime, odex_oat_file.get(), isa, - &odex_checksum_verified, - &odex_error_msg)) { - return odex_oat_file.release(); - } else { - if (odex_checksum_verified) { - // We can just relocate - should_patch_system = true; - odex_error_msg = "Image Patches are incorrect"; - } - if (odex_oat_file.get() != nullptr) { - have_system_odex = true; - } - } - } - - std::string cache_error_msg; - bool should_patch_cache = false; - bool cache_checksum_verified = false; - if (have_dalvik_cache) { - std::unique_ptr<OatFile> cache_oat_file(OatFile::Open(cache_filename, cache_filename, nullptr, - nullptr, - executable, &cache_error_msg)); - if (cache_oat_file.get() != nullptr && CheckOatFile(runtime, cache_oat_file.get(), isa, - &cache_checksum_verified, - &cache_error_msg)) { - return cache_oat_file.release(); - } else if (cache_checksum_verified) { - // We can just relocate - should_patch_cache = true; - cache_error_msg = "Image Patches are incorrect"; - } - } else if (have_android_data) { - // dalvik_cache does not exist but android data does. This means we should be able to create - // it, so we should try. - GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA), true); - } - - ret = nullptr; - std::string error_msg; - if (runtime->CanRelocate()) { - // Run relocation - gc::space::ImageSpace* space = Runtime::Current()->GetHeap()->GetImageSpace(); - if (space != nullptr) { - const std::string& image_location = space->GetImageLocation(); - if (odex_checksum_verified && should_patch_system) { - ret = PatchAndRetrieveOat(odex_filename, cache_filename, image_location, isa, &error_msg); - } else if (cache_checksum_verified && should_patch_cache) { - CHECK(have_dalvik_cache); - ret = PatchAndRetrieveOat(cache_filename, cache_filename, image_location, isa, &error_msg); - } - } else if (have_system_odex) { - ret = GetInterpretedOnlyOat(odex_filename, isa, &error_msg); - } - } - if (ret == nullptr && have_dalvik_cache && OS::FileExists(cache_filename.c_str())) { - // implicitly: were able to fine where the cached version is but we were unable to use it, - // either as a destination for relocation or to open a file. We should delete it if it is - // there. - if (TEMP_FAILURE_RETRY(unlink(cache_filename.c_str())) != 0) { - std::string rm_error_msg = StringPrintf("Failed to remove obsolete file from %s when " - "searching for dex file %s: %s", - cache_filename.c_str(), dex_location.c_str(), - strerror(errno)); - error_msgs->push_back(rm_error_msg); - VLOG(class_linker) << rm_error_msg; - // Let the caller know that we couldn't remove the obsolete file. - // This is a good indication that further writes may fail as well. - *obsolete_file_cleanup_failed = true; - } - } - if (ret == nullptr) { - VLOG(class_linker) << error_msg; - error_msgs->push_back(error_msg); - std::string relocation_msg; - if (runtime->CanRelocate()) { - relocation_msg = StringPrintf(" and relocation failed"); - } - if (have_dalvik_cache && cache_checksum_verified) { - error_msg = StringPrintf("Failed to open oat file from %s (error %s) or %s " - "(error %s)%s.", odex_filename.c_str(), odex_error_msg.c_str(), - cache_filename.c_str(), cache_error_msg.c_str(), - relocation_msg.c_str()); - } else { - error_msg = StringPrintf("Failed to open oat file from %s (error %s) (no " - "dalvik_cache availible)%s.", odex_filename.c_str(), - odex_error_msg.c_str(), relocation_msg.c_str()); - } - VLOG(class_linker) << error_msg; - error_msgs->push_back(error_msg); - } - return ret; -} - -const OatFile* ClassLinker::GetInterpretedOnlyOat(const std::string& oat_path, - InstructionSet isa, - std::string* error_msg) { - // We open it non-executable - std::unique_ptr<OatFile> output(OatFile::Open(oat_path, oat_path, nullptr, nullptr, false, error_msg)); - if (output.get() == nullptr) { - return nullptr; - } - if (!Runtime::Current()->GetHeap()->HasImageSpace() || - VerifyOatImageChecksum(output.get(), isa)) { - return output.release(); - } else { - *error_msg = StringPrintf("Could not use oat file '%s', image checksum failed to verify.", - oat_path.c_str()); - return nullptr; - } -} - -const OatFile* ClassLinker::PatchAndRetrieveOat(const std::string& input_oat, - const std::string& output_oat, - const std::string& image_location, - InstructionSet isa, - std::string* error_msg) { - Runtime* runtime = Runtime::Current(); - DCHECK(runtime != nullptr); - if (!runtime->GetHeap()->HasImageSpace()) { - // We don't have an image space so there is no point in trying to patchoat. - LOG(WARNING) << "Patching of oat file '" << input_oat << "' not attempted because we are " - << "running without an image. Attempting to use oat file for interpretation."; - return GetInterpretedOnlyOat(input_oat, isa, error_msg); - } - if (!runtime->IsDex2OatEnabled()) { - // We don't have dex2oat so we can assume we don't have patchoat either. We should just use the - // input_oat but make sure we only do interpretation on it's dex files. - LOG(WARNING) << "Patching of oat file '" << input_oat << "' not attempted due to dex2oat being " - << "disabled. Attempting to use oat file for interpretation"; - return GetInterpretedOnlyOat(input_oat, isa, error_msg); - } - Locks::mutator_lock_->AssertNotHeld(Thread::Current()); // Avoid starving GC. - std::string patchoat(runtime->GetPatchoatExecutable()); - - std::string isa_arg("--instruction-set="); - isa_arg += GetInstructionSetString(isa); - std::string input_oat_filename_arg("--input-oat-file="); - input_oat_filename_arg += input_oat; - std::string output_oat_filename_arg("--output-oat-file="); - output_oat_filename_arg += output_oat; - std::string patched_image_arg("--patched-image-location="); - patched_image_arg += image_location; - - std::vector<std::string> argv; - argv.push_back(patchoat); - argv.push_back(isa_arg); - argv.push_back(input_oat_filename_arg); - argv.push_back(output_oat_filename_arg); - argv.push_back(patched_image_arg); - - std::string command_line(Join(argv, ' ')); - LOG(INFO) << "Relocate Oat File: " << command_line; - bool success = Exec(argv, error_msg); - if (success) { - std::unique_ptr<OatFile> output(OatFile::Open(output_oat, output_oat, nullptr, nullptr, - !runtime->IsAotCompiler(), error_msg)); - bool checksum_verified = false; - if (output.get() != nullptr && CheckOatFile(runtime, output.get(), isa, &checksum_verified, - error_msg)) { - return output.release(); - } else if (output.get() != nullptr) { - *error_msg = StringPrintf("Patching of oat file '%s' succeeded " - "but output file '%s' failed verifcation: %s", - input_oat.c_str(), output_oat.c_str(), error_msg->c_str()); - } else { - *error_msg = StringPrintf("Patching of oat file '%s' succeeded " - "but was unable to open output file '%s': %s", - input_oat.c_str(), output_oat.c_str(), error_msg->c_str()); - } - } else if (!runtime->IsAotCompiler()) { - // patchoat failed which means we probably don't have enough room to place the output oat file, - // instead of failing we should just run the interpreter from the dex files in the input oat. - LOG(WARNING) << "Patching of oat file '" << input_oat << "' failed. Attempting to use oat file " - << "for interpretation. patchoat failure was: " << *error_msg; - return GetInterpretedOnlyOat(input_oat, isa, error_msg); - } else { - *error_msg = StringPrintf("Patching of oat file '%s to '%s' " - "failed: %s", input_oat.c_str(), output_oat.c_str(), - error_msg->c_str()); - } - return nullptr; -} - -bool ClassLinker::CheckOatFile(const Runtime* runtime, const OatFile* oat_file, InstructionSet isa, - bool* checksum_verified, - std::string* error_msg) { - const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); - if (image_space == nullptr) { - *error_msg = "No image space present"; - return false; - } - uint32_t real_image_checksum; - void* real_image_oat_offset; - int32_t real_patch_delta; - if (isa == runtime->GetInstructionSet()) { - const ImageHeader& image_header = image_space->GetImageHeader(); - real_image_checksum = image_header.GetOatChecksum(); - real_image_oat_offset = image_header.GetOatDataBegin(); - real_patch_delta = image_header.GetPatchDelta(); - } else { - std::unique_ptr<ImageHeader> image_header(gc::space::ImageSpace::ReadImageHeaderOrDie( - image_space->GetImageLocation().c_str(), isa)); - real_image_checksum = image_header->GetOatChecksum(); - real_image_oat_offset = image_header->GetOatDataBegin(); - real_patch_delta = image_header->GetPatchDelta(); - } - - const OatHeader& oat_header = oat_file->GetOatHeader(); - std::string compound_msg; - - uint32_t oat_image_checksum = oat_header.GetImageFileLocationOatChecksum(); - *checksum_verified = oat_image_checksum == real_image_checksum; - if (!*checksum_verified) { - StringAppendF(&compound_msg, " Oat Image Checksum Incorrect (expected 0x%x, received 0x%x)", - real_image_checksum, oat_image_checksum); - } - - bool offset_verified; - bool patch_delta_verified; - - if (!oat_file->IsPic()) { - // If an oat file is not PIC, we need to check that the image is at the expected location and - // patched in the same way. - void* oat_image_oat_offset = - reinterpret_cast<void*>(oat_header.GetImageFileLocationOatDataBegin()); - offset_verified = oat_image_oat_offset == real_image_oat_offset; - if (!offset_verified) { - StringAppendF(&compound_msg, " Oat Image oat offset incorrect (expected 0x%p, received 0x%p)", - real_image_oat_offset, oat_image_oat_offset); - } - - int32_t oat_patch_delta = oat_header.GetImagePatchDelta(); - patch_delta_verified = oat_patch_delta == real_patch_delta; - if (!patch_delta_verified) { - StringAppendF(&compound_msg, " Oat image patch delta incorrect (expected 0x%x, " - "received 0x%x)", real_patch_delta, oat_patch_delta); - } - } else { - // If an oat file is PIC, we ignore offset and patching delta. - offset_verified = true; - patch_delta_verified = true; - } - - bool ret = (*checksum_verified && offset_verified && patch_delta_verified); - if (!ret) { - *error_msg = "Oat file failed to verify:" + compound_msg; - } - return ret; -} - -const OatFile* ClassLinker::FindOatFileFromOatLocation(const std::string& oat_location, - std::string* error_msg) { - const OatFile* oat_file = FindOpenedOatFileFromOatLocation(oat_location); - if (oat_file != nullptr) { - return oat_file; - } - return OatFile::Open(oat_location, oat_location, nullptr, nullptr, - !Runtime::Current()->IsAotCompiler(), error_msg); -} - void ClassLinker::InitFromImageInterpretOnlyCallback(mirror::Object* obj, void* arg) { ClassLinker* class_linker = reinterpret_cast<ClassLinker*>(arg); DCHECK(obj != nullptr); diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 6570c5f02a..75fbdf3f59 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -313,33 +313,25 @@ class ClassLinker { LOCKS_EXCLUDED(dex_lock_) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - // Generate an oat file from a dex file - bool GenerateOatFile(const char* dex_filename, - int oat_fd, - const char* oat_cache_filename, - std::string* error_msg) - LOCKS_EXCLUDED(Locks::mutator_lock_); - - // Find or create the oat file holding dex_location. Then load all corresponding dex files - // (if multidex) into the given vector. - bool OpenDexFilesFromOat(const char* dex_location, const char* oat_location, - std::vector<std::string>* error_msgs, - std::vector<std::unique_ptr<const DexFile>>* dex_files) + // Finds or creates the oat file holding dex_location. Then loads and returns + // all corresponding dex files (there may be more than one dex file loaded + // in the case of multidex). + // This may return the original, unquickened dex files if the oat file could + // not be generated. + // + // Returns an empty vector if the dex files could not be loaded. In this + // case, there will be at least one error message returned describing why no + // dex files could not be loaded. The 'error_msgs' argument must not be + // null, regardless of whether there is an error or not. + // + // This method should not be called with the mutator_lock_ held, because it + // could end up starving GC if we need to generate or relocate any oat + // files. + std::vector<std::unique_ptr<const DexFile>> OpenDexFilesFromOat( + const char* dex_location, const char* oat_location, + std::vector<std::string>* error_msgs) LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_); - // Returns true if the given oat file has the same image checksum as the image it is paired with. - static bool VerifyOatImageChecksum(const OatFile* oat_file, const InstructionSet instruction_set); - // Returns true if the oat file checksums match with the image and the offsets are such that it - // could be loaded with it. - static bool VerifyOatChecksums(const OatFile* oat_file, const InstructionSet instruction_set, - std::string* error_msg); - // Returns true if oat file contains the dex file with the given location and checksum. - static bool VerifyOatAndDexFileChecksums(const OatFile* oat_file, - const char* dex_location, - uint32_t dex_location_checksum, - InstructionSet instruction_set, - std::string* error_msg); - // Allocate an instance of a java.lang.Object. mirror::Object* AllocObject(Thread* self) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -612,73 +604,9 @@ class ClassLinker { const uint32_t* dex_location_checksum) LOCKS_EXCLUDED(dex_lock_); - // Will open the oat file directly without relocating, even if we could/should do relocation. - const OatFile* FindOatFileFromOatLocation(const std::string& oat_location, - std::string* error_msg) - LOCKS_EXCLUDED(dex_lock_); - const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location) LOCKS_EXCLUDED(dex_lock_); - const OatFile* OpenOatFileFromDexLocation(const std::string& dex_location, - InstructionSet isa, - bool* already_opened, - bool* obsolete_file_cleanup_failed, - std::vector<std::string>* error_msg) - LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_); - - const OatFile* GetInterpretedOnlyOat(const std::string& oat_path, - InstructionSet isa, - std::string* error_msg); - - const OatFile* PatchAndRetrieveOat(const std::string& input, const std::string& output, - const std::string& image_location, InstructionSet isa, - std::string* error_msg) - LOCKS_EXCLUDED(Locks::mutator_lock_); - - bool CheckOatFile(const Runtime* runtime, const OatFile* oat_file, InstructionSet isa, - bool* checksum_verified, std::string* error_msg); - - // Note: will not register the oat file. - const OatFile* FindOatFileInOatLocationForDexFile(const char* dex_location, - uint32_t dex_location_checksum, - const char* oat_location, - std::string* error_msg) - LOCKS_EXCLUDED(dex_lock_); - - // Creates the oat file from the dex_location to the oat_location. Needs a file descriptor for - // the file to be written, which is assumed to be under a lock. - const OatFile* CreateOatFileForDexLocation(const char* dex_location, - int fd, const char* oat_location, - std::vector<std::string>* error_msgs) - LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_); - - // Finds an OatFile that contains a DexFile for the given a DexFile location. - // - // Note 1: this will not check open oat files, which are assumed to be stale when this is run. - // Note 2: Does not register the oat file. It is the caller's job to register if the file is to - // be kept. - const OatFile* FindOatFileContainingDexFileFromDexLocation(const char* dex_location, - const uint32_t* dex_location_checksum, - InstructionSet isa, - std::vector<std::string>* error_msgs, - bool* obsolete_file_cleanup_failed) - LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_); - - // Verifies: - // - that the oat file contains the dex file (with a matching checksum, which may be null if the - // file was pre-opted) - // - the checksums of the oat file (against the image space) - // - the checksum of the dex file against dex_location_checksum - // - that the dex file can be opened - // Returns true iff all verification succeed. - // - // The dex_location is the dex location as stored in the oat file header. - // (see DexFile::GetDexCanonicalLocation for a description of location conventions) - bool VerifyOatWithDexFile(const OatFile* oat_file, const char* dex_location, - const uint32_t* dex_location_checksum, - std::string* error_msg); - mirror::ArtMethod* CreateProxyConstructor(Thread* self, Handle<mirror::Class> klass, mirror::Class* proxy_class) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); @@ -803,7 +731,6 @@ class ClassLinker { friend class ImageWriter; // for GetClassRoots friend class ImageDumper; // for FindOpenedOatFileFromOatLocation - friend class ElfPatcher; // for FindOpenedOatFileForDexFile & FindOpenedOatFileFromOatLocation friend class JniCompilerTest; // for GetRuntimeQuickGenericJniStub friend class NoDex2OatTest; // for FindOpenedOatFileForDexFile friend class NoPatchoatTest; // for FindOpenedOatFileForDexFile diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index b7ffd609b7..1c8a8925d8 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -36,6 +36,7 @@ #include "gtest/gtest.h" #include "jni_internal.h" #include "mirror/class_loader.h" +#include "mem_map.h" #include "noop_compiler_callbacks.h" #include "os.h" #include "runtime-inl.h" @@ -194,6 +195,7 @@ std::string CommonRuntimeTest::GetCoreOatLocation() { std::unique_ptr<const DexFile> CommonRuntimeTest::LoadExpectSingleDexFile(const char* location) { std::vector<std::unique_ptr<const DexFile>> dex_files; std::string error_msg; + MemMap::Init(); if (!DexFile::Open(location, location, &error_msg, &dex_files)) { LOG(FATAL) << "Could not open .dex file '" << location << "': " << error_msg << "\n"; UNREACHABLE(); @@ -225,10 +227,12 @@ void CommonRuntimeTest::SetUp() { options.push_back(std::make_pair("compilercallbacks", callbacks_.get())); SetUpRuntimeOptions(&options); + PreRuntimeCreate(); if (!Runtime::Create(options, false)) { LOG(FATAL) << "Failed to create runtime"; return; } + PostRuntimeCreate(); runtime_.reset(Runtime::Current()); class_linker_ = runtime_->GetClassLinker(); class_linker_->FixupDexCaches(runtime_->GetResolutionMethod()); @@ -335,7 +339,7 @@ std::string CommonRuntimeTest::GetTestAndroidRoot() { #define ART_TARGET_NATIVETEST_DIR_STRING "" #endif -std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTest::OpenTestDexFiles(const char* name) { +std::string CommonRuntimeTest::GetTestDexFileName(const char* name) { CHECK(name != nullptr); std::string filename; if (IsHost()) { @@ -347,6 +351,11 @@ std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTest::OpenTestDexFiles( filename += "art-gtest-"; filename += name; filename += ".jar"; + return filename; +} + +std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTest::OpenTestDexFiles(const char* name) { + std::string filename = GetTestDexFileName(name); std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; bool success = DexFile::Open(filename.c_str(), filename.c_str(), &error_msg, &dex_files); diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 9efea84743..cce8485a42 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -101,11 +101,19 @@ class CommonRuntimeTest : public testing::Test { virtual void TearDown(); + // Called before the runtime is created. + virtual void PreRuntimeCreate() {} + + // Called after the runtime is created. + virtual void PostRuntimeCreate() {} + // Gets the path of the specified dex file for host or target. static std::string GetDexFileName(const std::string& jar_prefix); std::string GetTestAndroidRoot(); + std::string GetTestDexFileName(const char* name); + std::vector<std::unique_ptr<const DexFile>> OpenTestDexFiles(const char* name) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index e1fe3eb918..c182a4d9ad 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -16,31 +16,17 @@ #include "dalvik_system_DexFile.h" -#include <algorithm> -#include <set> -#include <fcntl.h> -#ifdef __linux__ -#include <sys/sendfile.h> -#else -#include <sys/socket.h> -#endif -#include <sys/stat.h> -#include <unistd.h> - #include "base/logging.h" #include "base/stl_util.h" #include "base/stringprintf.h" #include "class_linker.h" #include "common_throws.h" #include "dex_file-inl.h" -#include "gc/space/image_space.h" -#include "gc/space/space-inl.h" -#include "image.h" #include "jni_internal.h" #include "mirror/class_loader.h" #include "mirror/object-inl.h" #include "mirror/string.h" -#include "oat.h" +#include "oat_file_assistant.h" #include "os.h" #include "profiler.h" #include "runtime.h" @@ -51,11 +37,6 @@ #include "well_known_classes.h" #include "zip_archive.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wshadow" -#include "ScopedFd.h" -#pragma GCC diagnostic pop - namespace art { static std::unique_ptr<std::vector<const DexFile*>> @@ -182,10 +163,9 @@ static jobject DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSource std::vector<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> error_msgs; - bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs, - &dex_files); + dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs); - if (success || !dex_files.empty()) { + if (!dex_files.empty()) { jlongArray array = ConvertNativeToJavaArray(env, dex_files); if (array == nullptr) { ScopedObjectAccess soa(env); @@ -197,9 +177,6 @@ static jobject DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSource } return array; } else { - // The vector should be empty after a failed loading attempt. - DCHECK_EQ(0U, dex_files.size()); - ScopedObjectAccess soa(env); CHECK(!error_msgs.empty()); // The most important message is at the end. So set up nesting by going forward, which will @@ -320,40 +297,6 @@ static jobjectArray DexFile_getClassNameList(JNIEnv* env, jclass, jobject cookie return result; } -static void CopyProfileFile(const char* oldfile, const char* newfile) { - ScopedFd src(open(oldfile, O_RDONLY)); - if (src.get() == -1) { - PLOG(ERROR) << "Failed to open profile file " << oldfile - << ". My uid:gid is " << getuid() << ":" << getgid(); - return; - } - - struct stat stat_src; - if (fstat(src.get(), &stat_src) == -1) { - PLOG(ERROR) << "Failed to get stats for profile file " << oldfile - << ". My uid:gid is " << getuid() << ":" << getgid(); - return; - } - - // Create the copy with rw------- (only accessible by system) - ScopedFd dst(open(newfile, O_WRONLY|O_CREAT|O_TRUNC, 0600)); - if (dst.get() == -1) { - PLOG(ERROR) << "Failed to create/write prev profile file " << newfile - << ". My uid:gid is " << getuid() << ":" << getgid(); - return; - } - -#ifdef __linux__ - if (sendfile(dst.get(), src.get(), nullptr, stat_src.st_size) == -1) { -#else - off_t len; - if (sendfile(dst.get(), src.get(), 0, &len, nullptr, 0) == -1) { -#endif - PLOG(ERROR) << "Failed to copy profile file " << oldfile << " to " << newfile - << ". My uid:gid is " << getuid() << ":" << getgid(); - } -} - // Java: dalvik.system.DexFile.UP_TO_DATE static const jbyte kUpToDate = 0; // Java: dalvik.system.DexFile.DEXOPT_NEEDED @@ -361,102 +304,8 @@ static const jbyte kPatchoatNeeded = 1; // Java: dalvik.system.DexFile.PATCHOAT_NEEDED static const jbyte kDexoptNeeded = 2; -template <const bool kVerboseLogging, const bool kReasonLogging> -static jbyte IsDexOptNeededForFile(const std::string& oat_filename, const char* filename, - InstructionSet target_instruction_set, - bool* oat_is_pic) { - std::string error_msg; - std::unique_ptr<const OatFile> oat_file(OatFile::Open(oat_filename, oat_filename, nullptr, - nullptr, - false, &error_msg)); - if (oat_file.get() == nullptr) { - // Note that even though this is kDexoptNeeded, we use - // kVerboseLogging instead of the usual kReasonLogging since it is - // the common case on first boot and very spammy. - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded failed to open oat file '" << oat_filename - << "' for file location '" << filename << "': " << error_msg; - } - error_msg.clear(); - return kDexoptNeeded; - } - - // Pass-up the information about if this is PIC. - // TODO: Refactor this function to be less complicated. - *oat_is_pic = oat_file->IsPic(); - - bool should_relocate_if_possible = Runtime::Current()->ShouldRelocate(); - uint32_t location_checksum = 0; - const art::OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(filename, nullptr, - kReasonLogging); - if (oat_dex_file != nullptr) { - // If its not possible to read the classes.dex assume up-to-date as we won't be able to - // compile it anyway. - if (!DexFile::GetChecksum(filename, &location_checksum, &error_msg)) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded found precompiled stripped file: " - << filename << " for " << oat_filename << ": " << error_msg; - } - if (ClassLinker::VerifyOatChecksums(oat_file.get(), target_instruction_set, &error_msg)) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " is up-to-date for " << filename; - } - return kUpToDate; - } else if (should_relocate_if_possible && - ClassLinker::VerifyOatImageChecksum(oat_file.get(), target_instruction_set)) { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " needs to be relocated for " << filename; - } - return kPatchoatNeeded; - } else { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " is out of date for " << filename; - } - return kDexoptNeeded; - } - // If we get here the file is out of date and we should use the system one to relocate. - } else { - if (ClassLinker::VerifyOatAndDexFileChecksums(oat_file.get(), filename, location_checksum, - target_instruction_set, &error_msg)) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " is up-to-date for " << filename; - } - return kUpToDate; - } else if (location_checksum == oat_dex_file->GetDexFileLocationChecksum() - && should_relocate_if_possible - && ClassLinker::VerifyOatImageChecksum(oat_file.get(), target_instruction_set)) { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " needs to be relocated for " << filename; - } - return kPatchoatNeeded; - } else { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " is out of date for " << filename; - } - return kDexoptNeeded; - } - } - } else { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded file " << oat_filename - << " does not contain " << filename; - } - return kDexoptNeeded; - } -} - static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename, const char* pkgname, const char* instruction_set, const jboolean defer) { - // Spammy logging for kUpToDate - const bool kVerboseLogging = false; - // Logging of reason for returning kDexoptNeeded or kPatchoatNeeded. - const bool kReasonLogging = true; if ((filename == nullptr) || !OS::FileExists(filename)) { LOG(ERROR) << "DexFile_isDexOptNeeded file '" << filename << "' does not exist"; @@ -466,117 +315,6 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename, return kUpToDate; } - // Always treat elements of the bootclasspath as up-to-date. The - // fact that code is running at all means that this should be true. - Runtime* runtime = Runtime::Current(); - ClassLinker* class_linker = runtime->GetClassLinker(); - // TODO: We're assuming that the 64 and 32 bit runtimes have identical - // class paths. isDexOptNeeded will not necessarily be called on a runtime - // that has the same instruction set as the file being dexopted. - const std::vector<const DexFile*>& boot_class_path = class_linker->GetBootClassPath(); - for (size_t i = 0; i < boot_class_path.size(); i++) { - if (boot_class_path[i]->GetLocation() == filename) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeeded ignoring boot class path file: " << filename; - } - return kUpToDate; - } - } - - bool force_system_only = false; - bool require_system_version = false; - - // Check the profile file. We need to rerun dex2oat if the profile has changed significantly - // since the last time, or it's new. - // If the 'defer' argument is true then this will be retried later. In this case we - // need to make sure that the profile file copy is not made so that we will get the - // same result second time. - std::string profile_file; - std::string prev_profile_file; - bool should_copy_profile = false; - if (Runtime::Current()->GetProfilerOptions().IsEnabled() && (pkgname != nullptr)) { - profile_file = GetDalvikCacheOrDie("profiles", false /* create_if_absent */) - + std::string("/") + pkgname; - prev_profile_file = profile_file + std::string("@old"); - - struct stat profstat, prevstat; - int e1 = stat(profile_file.c_str(), &profstat); - int e1_errno = errno; - int e2 = stat(prev_profile_file.c_str(), &prevstat); - int e2_errno = errno; - if (e1 < 0) { - if (e1_errno != EACCES) { - // No profile file, need to run dex2oat, unless we find a file in system - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal profile file " << profile_file << " doesn't exist. " - << "Will check odex to see if we can find a working version."; - } - // Force it to only accept system files/files with versions in system. - require_system_version = true; - } else { - LOG(INFO) << "DexFile_isDexOptNeededInternal recieved EACCES trying to stat profile file " - << profile_file; - } - } else if (e2 == 0) { - // There is a previous profile file. Check if the profile has changed significantly. - // A change in profile is considered significant if X% (change_thr property) of the top K% - // (compile_thr property) samples has changed. - double top_k_threshold = Runtime::Current()->GetProfilerOptions().GetTopKThreshold(); - double change_threshold = Runtime::Current()->GetProfilerOptions().GetTopKChangeThreshold(); - double change_percent = 0.0; - ProfileFile new_profile, old_profile; - bool new_ok = new_profile.LoadFile(profile_file); - bool old_ok = old_profile.LoadFile(prev_profile_file); - if (!new_ok || !old_ok) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal Ignoring invalid profiles: " - << (new_ok ? "" : profile_file) << " " << (old_ok ? "" : prev_profile_file); - } - } else { - std::set<std::string> new_top_k, old_top_k; - new_profile.GetTopKSamples(new_top_k, top_k_threshold); - old_profile.GetTopKSamples(old_top_k, top_k_threshold); - if (new_top_k.empty()) { - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal empty profile: " << profile_file; - } - // If the new topK is empty we shouldn't optimize so we leave the change_percent at 0.0. - } else { - std::set<std::string> diff; - std::set_difference(new_top_k.begin(), new_top_k.end(), old_top_k.begin(), old_top_k.end(), - std::inserter(diff, diff.end())); - // TODO: consider using the usedPercentage instead of the plain diff count. - change_percent = 100.0 * static_cast<double>(diff.size()) / static_cast<double>(new_top_k.size()); - if (kVerboseLogging) { - std::set<std::string>::iterator end = diff.end(); - for (std::set<std::string>::iterator it = diff.begin(); it != end; it++) { - LOG(INFO) << "DexFile_isDexOptNeededInternal new in topK: " << *it; - } - } - } - } - - if (change_percent > change_threshold) { - if (kReasonLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal size of new profile file " << profile_file << - " is significantly different from old profile file " << prev_profile_file << " (top " - << top_k_threshold << "% samples changed in proportion of " << change_percent << "%)"; - } - should_copy_profile = !defer; - // Force us to only accept system files. - force_system_only = true; - } - } else if (e2_errno == ENOENT) { - // Previous profile does not exist. Make a copy of the current one. - if (kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal previous profile doesn't exist: " << prev_profile_file; - } - should_copy_profile = !defer; - } else { - PLOG(INFO) << "Unable to stat previous profile file " << prev_profile_file; - } - } - const InstructionSet target_instruction_set = GetInstructionSetFromString(instruction_set); if (target_instruction_set == kNone) { ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException")); @@ -585,75 +323,43 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename, return 0; } - // Get the filename for odex file next to the dex file. - std::string odex_filename(DexFilenameToOdexFilename(filename, target_instruction_set)); - // Get the filename for the dalvik-cache file - std::string cache_dir; - bool have_android_data = false; - bool dalvik_cache_exists = false; - bool is_global_cache = false; - GetDalvikCache(instruction_set, false, &cache_dir, &have_android_data, &dalvik_cache_exists, - &is_global_cache); - std::string cache_filename; // was cache_location - bool have_cache_filename = false; - if (dalvik_cache_exists) { - std::string error_msg; - have_cache_filename = GetDalvikCacheFilename(filename, cache_dir.c_str(), &cache_filename, - &error_msg); - if (!have_cache_filename && kVerboseLogging) { - LOG(INFO) << "DexFile_isDexOptNeededInternal failed to find cache file for dex file " << filename - << ": " << error_msg; - } - } - - bool should_relocate_if_possible = Runtime::Current()->ShouldRelocate(); - - jbyte dalvik_cache_decision = -1; - // Lets try the cache first (since we want to load from there since thats where the relocated - // versions will be). - if (have_cache_filename && !force_system_only) { - bool oat_is_pic; - // We can use the dalvik-cache if we find a good file. - dalvik_cache_decision = - IsDexOptNeededForFile<kVerboseLogging, kReasonLogging>(cache_filename, filename, - target_instruction_set, &oat_is_pic); - - // Apps that are compiled with --compile-pic never need to be patchoat-d - if (oat_is_pic && dalvik_cache_decision == kPatchoatNeeded) { - dalvik_cache_decision = kUpToDate; - } - // We will only return DexOptNeeded if both the cache and system return it. - if (dalvik_cache_decision != kDexoptNeeded && !require_system_version) { - CHECK(!(dalvik_cache_decision == kPatchoatNeeded && !should_relocate_if_possible)) - << "May not return PatchoatNeeded when patching is disabled."; - return dalvik_cache_decision; - } - // We couldn't find one thats easy. We should now try the system. - } + // TODO: Verify the dex location is well formed, and throw an IOException if + // not? - bool oat_is_pic; - jbyte system_decision = - IsDexOptNeededForFile<kVerboseLogging, kReasonLogging>(odex_filename, filename, - target_instruction_set, &oat_is_pic); - CHECK(!(system_decision == kPatchoatNeeded && !should_relocate_if_possible)) - << "May not return PatchoatNeeded when patching is disabled."; + OatFileAssistant oat_file_assistant(filename, target_instruction_set, false, pkgname); - // Apps that are compiled with --compile-pic never need to be patchoat-d - if (oat_is_pic && system_decision == kPatchoatNeeded) { - system_decision = kUpToDate; + // Always treat elements of the bootclasspath as up-to-date. + if (oat_file_assistant.IsInBootClassPath()) { + return kUpToDate; } - if (require_system_version && system_decision == kPatchoatNeeded - && dalvik_cache_decision == kUpToDate) { - // We have a version from system relocated to the cache. Return it. - return dalvik_cache_decision; + // TODO: Checking the profile should probably be done in the GetStatus() + // function. We have it here because GetStatus() should not be copying + // profile files. But who should be copying profile files? + if (oat_file_assistant.OdexFileIsOutOfDate()) { + // Needs recompile if profile has changed significantly. + if (Runtime::Current()->GetProfilerOptions().IsEnabled()) { + if (oat_file_assistant.IsProfileChangeSignificant()) { + if (!defer) { + oat_file_assistant.CopyProfileFile(); + } + return kDexoptNeeded; + } else if (oat_file_assistant.ProfileExists() + && !oat_file_assistant.OldProfileExists()) { + if (!defer) { + oat_file_assistant.CopyProfileFile(); + } + } + } } - if (should_copy_profile && system_decision == kDexoptNeeded) { - CopyProfileFile(profile_file.c_str(), prev_profile_file.c_str()); + OatFileAssistant::Status status = oat_file_assistant.GetStatus(); + switch (status) { + case OatFileAssistant::kUpToDate: return kUpToDate; + case OatFileAssistant::kNeedsRelocation: return kPatchoatNeeded; + case OatFileAssistant::kOutOfDate: return kDexoptNeeded; } - - return system_decision; + UNREACHABLE(); } static jbyte DexFile_isDexOptNeededInternal(JNIEnv* env, jclass, jstring javaFilename, diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc new file mode 100644 index 0000000000..f87fa4f8f4 --- /dev/null +++ b/runtime/oat_file_assistant.cc @@ -0,0 +1,952 @@ +/* + * Copyright (C) 2014 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 "oat_file_assistant.h" + +#include <fcntl.h> +#ifdef __linux__ +#include <sys/sendfile.h> +#else +#include <sys/socket.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <set> + +#include "base/logging.h" +#include "base/stringprintf.h" +#include "class_linker.h" +#include "gc/heap.h" +#include "gc/space/image_space.h" +#include "image.h" +#include "oat.h" +#include "os.h" +#include "profiler.h" +#include "runtime.h" +#include "ScopedFd.h" +#include "utils.h" + +namespace art { + +OatFileAssistant::OatFileAssistant(const char* dex_location, + const InstructionSet isa, + bool load_executable) + : OatFileAssistant(dex_location, nullptr, isa, load_executable, nullptr) { } + +OatFileAssistant::OatFileAssistant(const char* dex_location, + const char* oat_location, + const InstructionSet isa, + bool load_executable) + : OatFileAssistant(dex_location, oat_location, isa, load_executable, nullptr) { } + +OatFileAssistant::OatFileAssistant(const char* dex_location, + const InstructionSet isa, + bool load_executable, + const char* package_name) + : OatFileAssistant(dex_location, nullptr, isa, load_executable, package_name) { } + +OatFileAssistant::OatFileAssistant(const char* dex_location, + const char* oat_location, + const InstructionSet isa, + bool load_executable, + const char* package_name) + : dex_location_(dex_location), isa_(isa), + package_name_(package_name), load_executable_(load_executable) { + if (load_executable_ && isa != kRuntimeISA) { + LOG(WARNING) << "OatFileAssistant: Load executable specified, " + << "but isa is not kRuntimeISA. Will not attempt to load executable."; + load_executable_ = false; + } + + // If the user gave a target oat location, save that as the cached oat + // location now so we won't try to construct the default location later. + if (oat_location != nullptr) { + cached_oat_file_name_ = std::string(oat_location); + cached_oat_file_name_attempted_ = true; + cached_oat_file_name_found_ = true; + } + + // If there is no package name given, we will not be able to find any + // profiles associated with this dex location. Preemptively mark that to + // be the case, rather than trying to find and load the profiles later. + // Similarly, if profiling is disabled. + if (package_name == nullptr + || !Runtime::Current()->GetProfilerOptions().IsEnabled()) { + profile_load_attempted_ = true; + profile_load_succeeded_ = false; + old_profile_load_attempted_ = true; + old_profile_load_succeeded_ = false; + } +} + +OatFileAssistant::~OatFileAssistant() { + // Clean up the lock file. + if (lock_file_.get() != nullptr) { + lock_file_->Erase(); + TEMP_FAILURE_RETRY(unlink(lock_file_->GetPath().c_str())); + } +} + +bool OatFileAssistant::IsInBootClassPath() { + // Note: We check the current boot class path, regardless of the ISA + // specified by the user. This is okay, because the boot class path should + // be the same for all ISAs. + // TODO: Can we verify the boot class path is the same for all ISAs? + Runtime* runtime = Runtime::Current(); + ClassLinker* class_linker = runtime->GetClassLinker(); + const auto& boot_class_path = class_linker->GetBootClassPath(); + for (size_t i = 0; i < boot_class_path.size(); i++) { + if (boot_class_path[i]->GetLocation() == std::string(dex_location_)) { + VLOG(oat) << "Dex location " << dex_location_ << " is in boot class path"; + return true; + } + } + return false; +} + +bool OatFileAssistant::Lock(std::string* error_msg) { + CHECK(error_msg != nullptr); + CHECK(lock_file_.get() == nullptr) << "OatFileAssistant::Lock already acquired"; + + if (OatFileName() == nullptr) { + *error_msg = "Failed to determine lock file"; + return false; + } + std::string lock_file_name = *OatFileName() + ".flock"; + + lock_file_.reset(OS::CreateEmptyFile(lock_file_name.c_str())); + if (lock_file_.get() == nullptr) { + *error_msg = "Failed to create lock file " + lock_file_name; + return false; + } + + if (!flock_.Init(lock_file_.get(), error_msg)) { + TEMP_FAILURE_RETRY(unlink(lock_file_name.c_str())); + return false; + } + return true; +} + +OatFileAssistant::Status OatFileAssistant::GetStatus() { + // TODO: If the profiling code is ever restored, it's worth considering + // whether we should check to see if the profile is out of date here. + + if (OdexFileIsOutOfDate()) { + // The DEX file is not pre-compiled. + // TODO: What if the oat file is not out of date? Could we relocate it + // from itself? + return OatFileIsUpToDate() ? kUpToDate : kOutOfDate; + } else { + // The DEX file is pre-compiled. If the oat file isn't up to date, we can + // patch the pre-compiled version rather than recompiling. + if (OatFileIsUpToDate() || OdexFileIsUpToDate()) { + return kUpToDate; + } else { + return kNeedsRelocation; + } + } +} + +bool OatFileAssistant::MakeUpToDate(std::string* error_msg) { + switch (GetStatus()) { + case kUpToDate: return true; + case kNeedsRelocation: return RelocateOatFile(error_msg); + case kOutOfDate: return GenerateOatFile(error_msg); + } + UNREACHABLE(); +} + +std::unique_ptr<OatFile> OatFileAssistant::GetBestOatFile() { + if (OatFileIsUpToDate()) { + oat_file_released_ = true; + return std::move(cached_oat_file_); + } + + if (OdexFileIsUpToDate()) { + oat_file_released_ = true; + return std::move(cached_odex_file_); + } + + if (load_executable_) { + VLOG(oat) << "Oat File Assistant: No relocated oat file found," + << " attempting to fall back to interpreting oat file instead."; + + if (!OatFileIsOutOfDate()) { + load_executable_ = false; + ClearOatFileCache(); + if (!OatFileIsOutOfDate()) { + oat_file_released_ = true; + return std::move(cached_oat_file_); + } + } + + if (!OdexFileIsOutOfDate()) { + load_executable_ = false; + ClearOdexFileCache(); + if (!OdexFileIsOutOfDate()) { + oat_file_released_ = true; + return std::move(cached_odex_file_); + } + } + } + + return std::unique_ptr<OatFile>(); +} + +std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles( + const OatFile& oat_file, const char* dex_location) { + std::vector<std::unique_ptr<const DexFile>> dex_files; + + // Load the primary dex file. + std::string error_msg; + const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile( + dex_location, nullptr, false); + if (oat_dex_file == nullptr) { + LOG(WARNING) << "Attempt to load out-of-date oat file " + << oat_file.GetLocation() << " for dex location " << dex_location; + return std::vector<std::unique_ptr<const DexFile>>(); + } + + std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg); + if (dex_file.get() == nullptr) { + LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; + return std::vector<std::unique_ptr<const DexFile>>(); + } + dex_files.push_back(std::move(dex_file)); + + // Load secondary multidex files + for (int i = 1; ; i++) { + std::string secondary_dex_location = DexFile::GetMultiDexClassesDexName(i, dex_location); + oat_dex_file = oat_file.GetOatDexFile(secondary_dex_location.c_str(), nullptr, false); + if (oat_dex_file == NULL) { + // There are no more secondary dex files to load. + break; + } + + dex_file = oat_dex_file->OpenDexFile(&error_msg); + if (dex_file.get() == nullptr) { + LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; + return std::vector<std::unique_ptr<const DexFile>>(); + } + dex_files.push_back(std::move(dex_file)); + } + return dex_files; +} + +const std::string* OatFileAssistant::OdexFileName() { + if (!cached_odex_file_name_attempted_) { + CHECK(dex_location_ != nullptr) << "OatFileAssistant: null dex location"; + cached_odex_file_name_attempted_ = true; + + std::string error_msg; + cached_odex_file_name_found_ = DexFilenameToOdexFilename( + dex_location_, isa_, &cached_odex_file_name_, &error_msg); + if (!cached_odex_file_name_found_) { + // If we can't figure out the odex file, we treat it as if the odex + // file was inaccessible. + LOG(WARNING) << "Failed to determine odex file name: " << error_msg; + } + } + return cached_odex_file_name_found_ ? &cached_odex_file_name_ : nullptr; +} + +bool OatFileAssistant::OdexFileExists() { + return GetOdexFile() != nullptr; +} + +OatFileAssistant::Status OatFileAssistant::OdexFileStatus() { + if (OdexFileIsOutOfDate()) { + return kOutOfDate; + } + if (OdexFileIsUpToDate()) { + return kUpToDate; + } + return kNeedsRelocation; +} + +bool OatFileAssistant::OdexFileIsOutOfDate() { + if (!odex_file_is_out_of_date_attempted_) { + odex_file_is_out_of_date_attempted_ = true; + const OatFile* odex_file = GetOdexFile(); + if (odex_file == nullptr) { + cached_odex_file_is_out_of_date_ = true; + } else { + cached_odex_file_is_out_of_date_ = GivenOatFileIsOutOfDate(*odex_file); + } + } + return cached_odex_file_is_out_of_date_; +} + +bool OatFileAssistant::OdexFileNeedsRelocation() { + return OdexFileStatus() == kNeedsRelocation; +} + +bool OatFileAssistant::OdexFileIsUpToDate() { + if (!odex_file_is_up_to_date_attempted_) { + odex_file_is_up_to_date_attempted_ = true; + const OatFile* odex_file = GetOdexFile(); + if (odex_file == nullptr) { + cached_odex_file_is_up_to_date_ = false; + } else { + cached_odex_file_is_up_to_date_ = GivenOatFileIsUpToDate(*odex_file); + } + } + return cached_odex_file_is_up_to_date_; +} + +const std::string* OatFileAssistant::OatFileName() { + if (!cached_oat_file_name_attempted_) { + cached_oat_file_name_attempted_ = true; + + // Compute the oat file name from the dex location. + CHECK(dex_location_ != nullptr) << "OatFileAssistant: null dex location"; + + // TODO: The oat file assistant should be the definitive place for + // determining the oat file name from the dex location, not + // GetDalvikCacheFilename. + std::string cache_dir = StringPrintf("%s%s", + DalvikCacheDirectory().c_str(), GetInstructionSetString(isa_)); + std::string error_msg; + cached_oat_file_name_found_ = GetDalvikCacheFilename(dex_location_, + cache_dir.c_str(), &cached_oat_file_name_, &error_msg); + if (!cached_oat_file_name_found_) { + // If we can't determine the oat file name, we treat the oat file as + // inaccessible. + LOG(WARNING) << "Failed to determine oat file name for dex location " + << dex_location_ << ": " << error_msg; + } + } + return cached_oat_file_name_found_ ? &cached_oat_file_name_ : nullptr; +} + +bool OatFileAssistant::OatFileExists() { + return GetOatFile() != nullptr; +} + +OatFileAssistant::Status OatFileAssistant::OatFileStatus() { + if (OatFileIsOutOfDate()) { + return kOutOfDate; + } + if (OatFileIsUpToDate()) { + return kUpToDate; + } + return kNeedsRelocation; +} + +bool OatFileAssistant::OatFileIsOutOfDate() { + if (!oat_file_is_out_of_date_attempted_) { + oat_file_is_out_of_date_attempted_ = true; + const OatFile* oat_file = GetOatFile(); + if (oat_file == nullptr) { + cached_oat_file_is_out_of_date_ = true; + } else { + cached_oat_file_is_out_of_date_ = GivenOatFileIsOutOfDate(*oat_file); + } + } + return cached_oat_file_is_out_of_date_; +} + +bool OatFileAssistant::OatFileNeedsRelocation() { + return OatFileStatus() == kNeedsRelocation; +} + +bool OatFileAssistant::OatFileIsUpToDate() { + if (!oat_file_is_up_to_date_attempted_) { + oat_file_is_up_to_date_attempted_ = true; + const OatFile* oat_file = GetOatFile(); + if (oat_file == nullptr) { + cached_oat_file_is_up_to_date_ = false; + } else { + cached_oat_file_is_up_to_date_ = GivenOatFileIsUpToDate(*oat_file); + } + } + return cached_oat_file_is_up_to_date_; +} + +OatFileAssistant::Status OatFileAssistant::GivenOatFileStatus(const OatFile& file) { + // TODO: This could cause GivenOatFileIsOutOfDate to be called twice, which + // is more work than we need to do. If performance becomes a concern, and + // this method is actually called, this should be fixed. + if (GivenOatFileIsOutOfDate(file)) { + return kOutOfDate; + } + if (GivenOatFileIsUpToDate(file)) { + return kUpToDate; + } + return kNeedsRelocation; +} + +bool OatFileAssistant::GivenOatFileIsOutOfDate(const OatFile& file) { + // Verify the dex checksum. + // Note: GetOatDexFile will return NULL if the dex checksum doesn't match + // what we provide, which verifies the primary dex checksum for us. + const uint32_t* dex_checksum_pointer = GetRequiredDexChecksum(); + const OatFile::OatDexFile* oat_dex_file = file.GetOatDexFile( + dex_location_, dex_checksum_pointer, false); + if (oat_dex_file == NULL) { + return true; + } + + // Verify the dex checksums for any secondary multidex files + for (int i = 1; ; i++) { + std::string secondary_dex_location + = DexFile::GetMultiDexClassesDexName(i, dex_location_); + const OatFile::OatDexFile* secondary_oat_dex_file + = file.GetOatDexFile(secondary_dex_location.c_str(), nullptr, false); + if (secondary_oat_dex_file == NULL) { + // There are no more secondary dex files to check. + break; + } + + std::string error_msg; + uint32_t expected_secondary_checksum = 0; + if (DexFile::GetChecksum(secondary_dex_location.c_str(), + &expected_secondary_checksum, &error_msg)) { + uint32_t actual_secondary_checksum + = secondary_oat_dex_file->GetDexFileLocationChecksum(); + if (expected_secondary_checksum != actual_secondary_checksum) { + VLOG(oat) << "Dex checksum does not match for secondary dex: " + << secondary_dex_location + << ". Expected: " << expected_secondary_checksum + << ", Actual: " << actual_secondary_checksum; + return false; + } + } else { + // If we can't get the checksum for the secondary location, we assume + // the dex checksum is up to date for this and all other secondary dex + // files. + break; + } + } + + // Verify the image checksum + const ImageInfo* image_info = GetImageInfo(); + if (image_info == nullptr) { + VLOG(oat) << "No image for oat image checksum to match against."; + return true; + } + + if (file.GetOatHeader().GetImageFileLocationOatChecksum() != image_info->oat_checksum) { + VLOG(oat) << "Oat image checksum does not match image checksum."; + return true; + } + + // The checksums are all good; the dex file is not out of date. + return false; +} + +bool OatFileAssistant::GivenOatFileNeedsRelocation(const OatFile& file) { + return GivenOatFileStatus(file) == kNeedsRelocation; +} + +bool OatFileAssistant::GivenOatFileIsUpToDate(const OatFile& file) { + if (GivenOatFileIsOutOfDate(file)) { + return false; + } + + if (file.IsPic()) { + return true; + } + + const ImageInfo* image_info = GetImageInfo(); + if (image_info == nullptr) { + VLOG(oat) << "No image for to check oat relocation against."; + return false; + } + + // Verify the oat_data_begin recorded for the image in the oat file matches + // the actual oat_data_begin for boot.oat in the image. + const OatHeader& oat_header = file.GetOatHeader(); + uintptr_t oat_data_begin = oat_header.GetImageFileLocationOatDataBegin(); + if (oat_data_begin != image_info->oat_data_begin) { + VLOG(oat) << file.GetLocation() << + ": Oat file image oat_data_begin (" << oat_data_begin << ")" + << " does not match actual image oat_data_begin (" + << image_info->oat_data_begin << ")"; + return false; + } + + // Verify the oat_patch_delta recorded for the image in the oat file matches + // the actual oat_patch_delta for the image. + int32_t oat_patch_delta = oat_header.GetImagePatchDelta(); + if (oat_patch_delta != image_info->patch_delta) { + VLOG(oat) << file.GetLocation() << + ": Oat file image patch delta (" << oat_patch_delta << ")" + << " does not match actual image patch delta (" + << image_info->patch_delta << ")"; + return false; + } + return true; +} + +bool OatFileAssistant::ProfileExists() { + return GetProfile() != nullptr; +} + +bool OatFileAssistant::OldProfileExists() { + return GetOldProfile() != nullptr; +} + +// TODO: The IsProfileChangeSignificant implementation was copied from likely +// bit-rotted code. +bool OatFileAssistant::IsProfileChangeSignificant() { + ProfileFile* profile = GetProfile(); + if (profile == nullptr) { + return false; + } + + ProfileFile* old_profile = GetOldProfile(); + if (old_profile == nullptr) { + return false; + } + + // TODO: The following code to compare two profile files should live with + // the rest of the profiler code, not the oat file assistant code. + + // A change in profile is considered significant if X% (change_thr property) + // of the top K% (compile_thr property) samples has changed. + const ProfilerOptions& options = Runtime::Current()->GetProfilerOptions(); + const double top_k_threshold = options.GetTopKThreshold(); + const double change_threshold = options.GetTopKChangeThreshold(); + std::set<std::string> top_k, old_top_k; + profile->GetTopKSamples(top_k, top_k_threshold); + old_profile->GetTopKSamples(old_top_k, top_k_threshold); + std::set<std::string> diff; + std::set_difference(top_k.begin(), top_k.end(), old_top_k.begin(), + old_top_k.end(), std::inserter(diff, diff.end())); + + // TODO: consider using the usedPercentage instead of the plain diff count. + double change_percent = 100.0 * static_cast<double>(diff.size()) + / static_cast<double>(top_k.size()); + std::set<std::string>::iterator end = diff.end(); + for (std::set<std::string>::iterator it = diff.begin(); it != end; it++) { + VLOG(oat) << "Profile new in topK: " << *it; + } + + if (change_percent > change_threshold) { + VLOG(oat) << "Oat File Assistant: Profile for " << dex_location_ + << "has changed significantly: (top " + << top_k_threshold << "% samples changed in proportion of " + << change_percent << "%)"; + return true; + } + return false; +} + +// TODO: The CopyProfileFile implementation was copied from likely bit-rotted +// code. +void OatFileAssistant::CopyProfileFile() { + if (!ProfileExists()) { + return; + } + + std::string profile_name = ProfileFileName(); + std::string old_profile_name = OldProfileFileName(); + + ScopedFd src(open(old_profile_name.c_str(), O_RDONLY)); + if (src.get() == -1) { + PLOG(WARNING) << "Failed to open profile file " << old_profile_name + << ". My uid:gid is " << getuid() << ":" << getgid(); + return; + } + + struct stat stat_src; + if (fstat(src.get(), &stat_src) == -1) { + PLOG(WARNING) << "Failed to get stats for profile file " << old_profile_name + << ". My uid:gid is " << getuid() << ":" << getgid(); + return; + } + + // Create the copy with rw------- (only accessible by system) + ScopedFd dst(open(profile_name.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600)); + if (dst.get() == -1) { + PLOG(WARNING) << "Failed to create/write prev profile file " << profile_name + << ". My uid:gid is " << getuid() << ":" << getgid(); + return; + } + +#ifdef __linux__ + if (sendfile(dst.get(), src.get(), nullptr, stat_src.st_size) == -1) { +#else + off_t len; + if (sendfile(dst.get(), src.get(), 0, &len, nullptr, 0) == -1) { +#endif + PLOG(WARNING) << "Failed to copy profile file " << old_profile_name + << " to " << profile_name << ". My uid:gid is " << getuid() + << ":" << getgid(); + } +} + +bool OatFileAssistant::RelocateOatFile(std::string* error_msg) { + CHECK(error_msg != nullptr); + + if (OdexFileName() == nullptr) { + *error_msg = "Patching of oat file for dex location " + + std::string(dex_location_) + + " not attempted because the odex file name could not be determined."; + return false; + } + const std::string& odex_file_name = *OdexFileName(); + + if (OatFileName() == nullptr) { + *error_msg = "Patching of oat file for dex location " + + std::string(dex_location_) + + " not attempted because the oat file name could not be determined."; + return false; + } + const std::string& oat_file_name = *OatFileName(); + + const ImageInfo* image_info = GetImageInfo(); + Runtime* runtime = Runtime::Current(); + if (image_info == nullptr) { + *error_msg = "Patching of oat file " + oat_file_name + + " not attempted because no image location was found."; + return false; + } + + if (!runtime->IsDex2OatEnabled()) { + *error_msg = "Patching of oat file " + oat_file_name + + " not attempted because dex2oat is disabled"; + return false; + } + + std::vector<std::string> argv; + argv.push_back(runtime->GetPatchoatExecutable()); + argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(isa_))); + argv.push_back("--input-oat-file=" + odex_file_name); + argv.push_back("--output-oat-file=" + oat_file_name); + argv.push_back("--patched-image-location=" + image_info->location); + + std::string command_line(Join(argv, ' ')); + if (!Exec(argv, error_msg)) { + // Manually delete the file. This ensures there is no garbage left over if + // the process unexpectedly died. + TEMP_FAILURE_RETRY(unlink(oat_file_name.c_str())); + return false; + } + + // Mark that the oat file has changed and we should try to reload. + ClearOatFileCache(); + return true; +} + +bool OatFileAssistant::GenerateOatFile(std::string* error_msg) { + CHECK(error_msg != nullptr); + + if (OatFileName() == nullptr) { + *error_msg = "Generation of oat file for dex location " + + std::string(dex_location_) + + " not attempted because the oat file name could not be determined."; + return false; + } + const std::string& oat_file_name = *OatFileName(); + + Runtime* runtime = Runtime::Current(); + if (!runtime->IsDex2OatEnabled()) { + *error_msg = "Generation of oat file " + oat_file_name + + " not attempted because dex2oat is disabled"; + return false; + } + + std::vector<std::string> args; + args.push_back("--dex-file=" + std::string(dex_location_)); + args.push_back("--oat-file=" + 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? + if (!OS::FileExists(dex_location_)) { + *error_msg = "Dex location " + std::string(dex_location_) + " does not exists."; + return false; + } + + if (!Dex2Oat(args, error_msg)) { + // Manually delete the file. This ensures there is no garbage left over if + // the process unexpectedly died. + TEMP_FAILURE_RETRY(unlink(oat_file_name.c_str())); + return false; + } + + // Mark that the oat file has changed and we should try to reload. + ClearOatFileCache(); + return true; +} + +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()); + argv.push_back("--runtime-arg"); + argv.push_back("-classpath"); + argv.push_back("--runtime-arg"); + argv.push_back(runtime->GetClassPathString()); + runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); + + if (!runtime->IsVerificationEnabled()) { + argv.push_back("--compiler-filter=verify-none"); + } + + 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(Join(argv, ' ')); + return Exec(argv, error_msg); +} + +bool OatFileAssistant::DexFilenameToOdexFilename(const std::string& location, + InstructionSet isa, std::string* odex_filename, std::string* error_msg) { + CHECK(odex_filename != nullptr); + CHECK(error_msg != nullptr); + + // The odex file name is formed by replacing the dex_location extension with + // .odex and inserting an isa directory. For example: + // location = /foo/bar/baz.jar + // odex_location = /foo/bar/<isa>/baz.odex + + // Find the directory portion of the dex location and add the isa directory. + size_t pos = location.rfind('/'); + if (pos == std::string::npos) { + *error_msg = "Dex location " + location + " has no directory."; + return false; + } + std::string dir = location.substr(0, pos+1); + dir += std::string(GetInstructionSetString(isa)); + + // Find the file portion of the dex location. + std::string file; + if (pos == std::string::npos) { + file = location; + } else { + file = location.substr(pos+1); + } + + // Get the base part of the file without the extension. + pos = file.rfind('.'); + if (pos == std::string::npos) { + *error_msg = "Dex location " + location + " has no extension."; + return false; + } + std::string base = file.substr(0, pos); + + *odex_filename = dir + "/" + base + ".odex"; + return true; +} + +std::string OatFileAssistant::DalvikCacheDirectory() { + // Note: We don't cache this, because it will only be called once by + // OatFileName, and we don't care about the performance of the profiling + // code, which isn't used in practice. + + // TODO: The work done in GetDalvikCache is overkill for what we need. + // Ideally a new API for getting the DalvikCacheDirectory the way we want + // (without existence testing, creation, or death) is provided with the rest + // of the GetDalvikCache family of functions. Until such an API is in place, + // we use GetDalvikCache to avoid duplicating the logic for determining the + // dalvik cache directory. + std::string result; + bool have_android_data; + bool dalvik_cache_exists; + bool is_global_cache; + GetDalvikCache("", false, &result, &have_android_data, &dalvik_cache_exists, &is_global_cache); + return result; +} + +std::string OatFileAssistant::ProfileFileName() { + if (package_name_ != nullptr) { + return DalvikCacheDirectory() + std::string("profiles/") + package_name_; + } + return ""; +} + +std::string OatFileAssistant::OldProfileFileName() { + std::string profile_name = ProfileFileName(); + if (profile_name.empty()) { + return ""; + } + return profile_name + "@old"; +} + +std::string OatFileAssistant::ImageLocation() { + Runtime* runtime = Runtime::Current(); + const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); + if (image_space == nullptr) { + return ""; + } + return image_space->GetImageLocation(); +} + +const uint32_t* OatFileAssistant::GetRequiredDexChecksum() { + if (!required_dex_checksum_attempted) { + required_dex_checksum_attempted = true; + required_dex_checksum_found = false; + std::string error_msg; + CHECK(dex_location_ != nullptr) << "OatFileAssistant provided no dex location"; + if (DexFile::GetChecksum(dex_location_, &cached_required_dex_checksum, &error_msg)) { + required_dex_checksum_found = true; + } else { + // This can happen if the original dex file has been stripped from the + // apk. + VLOG(oat) << "OatFileAssistant: " << error_msg; + + // Get the checksum from the odex if we can. + const OatFile* odex_file = GetOdexFile(); + if (odex_file != nullptr) { + const OatFile::OatDexFile* odex_dex_file = odex_file->GetOatDexFile( + dex_location_, nullptr, false); + if (odex_dex_file != nullptr) { + cached_required_dex_checksum = odex_dex_file->GetDexFileLocationChecksum(); + required_dex_checksum_found = true; + } + } + } + } + return required_dex_checksum_found ? &cached_required_dex_checksum : nullptr; +} + +const OatFile* OatFileAssistant::GetOdexFile() { + CHECK(!oat_file_released_) << "OdexFile called after oat file released."; + if (!odex_file_load_attempted_) { + odex_file_load_attempted_ = true; + if (OdexFileName() != nullptr) { + const std::string& odex_file_name = *OdexFileName(); + std::string error_msg; + cached_odex_file_.reset(OatFile::Open(odex_file_name.c_str(), + odex_file_name.c_str(), nullptr, nullptr, load_executable_, + &error_msg)); + if (cached_odex_file_.get() == nullptr) { + VLOG(oat) << "OatFileAssistant test for existing pre-compiled oat file " + << odex_file_name << ": " << error_msg; + } + } + } + return cached_odex_file_.get(); +} + +void OatFileAssistant::ClearOdexFileCache() { + odex_file_load_attempted_ = false; + cached_odex_file_.reset(); + odex_file_is_out_of_date_attempted_ = false; + odex_file_is_up_to_date_attempted_ = false; +} + +const OatFile* OatFileAssistant::GetOatFile() { + CHECK(!oat_file_released_) << "OatFile called after oat file released."; + if (!oat_file_load_attempted_) { + oat_file_load_attempted_ = true; + if (OatFileName() != nullptr) { + const std::string& oat_file_name = *OatFileName(); + std::string error_msg; + cached_oat_file_.reset(OatFile::Open(oat_file_name.c_str(), + oat_file_name.c_str(), nullptr, nullptr, load_executable_, &error_msg)); + if (cached_oat_file_.get() == nullptr) { + VLOG(oat) << "OatFileAssistant test for existing oat file " + << oat_file_name << ": " << error_msg; + } + } + } + return cached_oat_file_.get(); +} + +void OatFileAssistant::ClearOatFileCache() { + oat_file_load_attempted_ = false; + cached_oat_file_.reset(); + oat_file_is_out_of_date_attempted_ = false; + oat_file_is_up_to_date_attempted_ = false; +} + +const OatFileAssistant::ImageInfo* OatFileAssistant::GetImageInfo() { + if (!image_info_load_attempted_) { + image_info_load_attempted_ = true; + + Runtime* runtime = Runtime::Current(); + const gc::space::ImageSpace* image_space = runtime->GetHeap()->GetImageSpace(); + if (image_space != nullptr) { + cached_image_info_.location = image_space->GetImageLocation(); + + if (isa_ == kRuntimeISA) { + const ImageHeader& image_header = image_space->GetImageHeader(); + cached_image_info_.oat_checksum = image_header.GetOatChecksum(); + cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header.GetOatDataBegin()); + cached_image_info_.patch_delta = image_header.GetPatchDelta(); + } else { + std::unique_ptr<ImageHeader> image_header( + gc::space::ImageSpace::ReadImageHeaderOrDie( + cached_image_info_.location.c_str(), isa_)); + cached_image_info_.oat_checksum = image_header->GetOatChecksum(); + cached_image_info_.oat_data_begin = reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()); + cached_image_info_.patch_delta = image_header->GetPatchDelta(); + } + } + image_info_load_succeeded_ = (image_space != nullptr); + } + return image_info_load_succeeded_ ? &cached_image_info_ : nullptr; +} + +ProfileFile* OatFileAssistant::GetProfile() { + if (!profile_load_attempted_) { + CHECK(package_name_ != nullptr) + << "pakage_name_ is nullptr: " + << "profile_load_attempted_ should have been true"; + profile_load_attempted_ = true; + std::string profile_name = ProfileFileName(); + if (!profile_name.empty()) { + profile_load_succeeded_ = cached_profile_.LoadFile(profile_name); + } + } + return profile_load_succeeded_ ? &cached_profile_ : nullptr; +} + +ProfileFile* OatFileAssistant::GetOldProfile() { + if (!old_profile_load_attempted_) { + CHECK(package_name_ != nullptr) + << "pakage_name_ is nullptr: " + << "old_profile_load_attempted_ should have been true"; + old_profile_load_attempted_ = true; + std::string old_profile_name = OldProfileFileName(); + if (!old_profile_name.empty()) { + old_profile_load_succeeded_ = cached_old_profile_.LoadFile(old_profile_name); + } + } + return old_profile_load_succeeded_ ? &cached_old_profile_ : nullptr; +} + +} // namespace art + diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h new file mode 100644 index 0000000000..958b44048d --- /dev/null +++ b/runtime/oat_file_assistant.h @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2014 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_RUNTIME_OAT_FILE_ASSISTANT_H_ +#define ART_RUNTIME_OAT_FILE_ASSISTANT_H_ + +#include <cstdint> +#include <memory> +#include <string> + +#include "arch/instruction_set.h" +#include "base/scoped_flock.h" +#include "base/unix_file/fd_file.h" +#include "oat_file.h" +#include "os.h" +#include "profiler.h" + +namespace art { + +// Class for assisting with oat file management. +// +// This class collects common utilities for determining the status of an oat +// file on the device, updating the oat file, and loading the oat file. +// +// The oat file assistant is intended to be used with dex locations not on the +// boot class path. See the IsInBootClassPath method for a way to check if the +// dex location is in the boot class path. +// +// TODO: All the profiling related code is old and untested. It should either +// be restored and tested, or removed. +class OatFileAssistant { + public: + enum Status { + // kOutOfDate - An oat file is said to be out of date if the file does not + // exist, or is out of date with respect to the dex file or boot image. + kOutOfDate, + + // kNeedsRelocation - An oat file is said to need relocation if the code + // is up to date, but not yet properly relocated for address space layout + // randomization (ASLR). In this case, the oat file is neither "out of + // date" nor "up to date". + kNeedsRelocation, + + // kUpToDate - An oat file is said to be up to date if it is not out of + // date and has been properly relocated for the purposes of ASLR. + kUpToDate, + }; + + // Constructs an OatFileAssistant object to assist the oat file + // corresponding to the given dex location with the target instruction set. + // + // The dex_location must not be NULL and should remain available and + // unchanged for the duration of the lifetime of the OatFileAssistant object. + // Typically the dex_location is the absolute path to the original, + // un-optimized dex file. + // + // + // Note: Currently the dex_location must have an extension. + // TODO: Relax this restriction? + // + // The isa should be either the 32 bit or 64 bit variant for the current + // device. For example, on an arm device, use arm or arm64. An oat file can + // be loaded executable only if the ISA matches the current runtime. + OatFileAssistant(const char* dex_location, const InstructionSet isa, + bool load_executable); + + // Constructs an OatFileAssistant, providing an explicit target oat_location + // to use instead of the standard oat location. + OatFileAssistant(const char* dex_location, const char* oat_location, + const InstructionSet isa, bool load_executable); + + // Constructs an OatFileAssistant, providing an additional package_name used + // solely for the purpose of locating profile files. + // + // TODO: Why is the name of the profile file based on the package name and + // not the dex location? If there is no technical reason the dex_location + // can't be used, we should prefer that instead. + OatFileAssistant(const char* dex_location, const InstructionSet isa, + bool load_executable, const char* package_name); + + // Constructs an OatFileAssistant with user specified oat location and a + // package name. + OatFileAssistant(const char* dex_location, const char* oat_location, + const InstructionSet isa, bool load_executable, + const char* package_name); + + ~OatFileAssistant(); + + // Returns true if the dex location refers to an element of the boot class + // 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); + + // Returns the overall compilation status for the given dex location. + Status GetStatus(); + + // Attempts to generate or relocate the oat file as needed to make it up to + // date. + // 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 nullptr. + bool MakeUpToDate(std::string* error_msg); + + // Returns an oat file that can be used for loading dex files. + // Returns nullptr if no suitable oat file was found. + // + // After this call, no other methods of the OatFileAssistant should be + // called, because access to the loaded oat file has been taken away from + // the OatFileAssistant object. + std::unique_ptr<OatFile> GetBestOatFile(); + + // Loads the dex files in the given oat file for the given dex location. + // The oat file should be up to date for the given dex location. + // This loads multiple dex files in the case of multidex. + // Returns an empty vector if no dex files for that location could be loaded + // from the oat file. + // + // The caller is responsible for freeing the dex_files returned, if any. The + // dex_files will only remain valid as long as the oat_file is valid. + static std::vector<std::unique_ptr<const DexFile>> LoadDexFiles( + const OatFile& oat_file, const char* dex_location); + + // If the dex file has been pre-compiled on the host, the compiled oat file + // will have the extension .odex, and is referred to as the odex file. + // It is called odex for legacy reasons; the file is really an oat file. The + // odex file will typically have a patch delta of 0 and need to be relocated + // before use for the purposes of ASLR. + // These methods return the location and status of the odex file for the dex + // location. + // Notes: + // * OdexFileName may return null if the odex file name could not be + // determined. + const std::string* OdexFileName(); + bool OdexFileExists(); + Status OdexFileStatus(); + bool OdexFileIsOutOfDate(); + bool OdexFileNeedsRelocation(); + bool OdexFileIsUpToDate(); + + // When the dex files is compiled on the target device, the oat file is the + // result. The oat file will have been relocated to some + // (possibly-out-of-date) offset for ASLR. + // These methods return the location and status of the target oat file for + // the dex location. + // + // Notes: + // * To get the overall status of the compiled code for this dex_location, + // use the GetStatus() method, not the OatFileStatus() method. + // * OatFileName may return null if the oat file name could not be + // determined. + const std::string* OatFileName(); + bool OatFileExists(); + Status OatFileStatus(); + bool OatFileIsOutOfDate(); + bool OatFileNeedsRelocation(); + bool OatFileIsUpToDate(); + + // These methods return the status for a given opened oat file with respect + // to the dex location. + Status GivenOatFileStatus(const OatFile& file); + bool GivenOatFileIsOutOfDate(const OatFile& file); + bool GivenOatFileNeedsRelocation(const OatFile& file); + bool GivenOatFileIsUpToDate(const OatFile& file); + + // Returns true if there is an accessible profile associated with the dex + // location. + // This returns false if profiling is disabled. + bool ProfileExists(); + + // The old profile is a file containing a previous snapshot of profiling + // information associated with the dex file code. This is used to track how + // the profiling information has changed over time. + // + // Returns true if there is an accessible old profile associated with the + // dex location. + // This returns false if profiling is disabled. + bool OldProfileExists(); + + // Returns true if there has been a significant change between the old + // profile and the current profile. + // This returns false if profiling is disabled. + bool IsProfileChangeSignificant(); + + // Copy the current profile to the old profile location. + void CopyProfileFile(); + + // Generates the oat file by relocation from the odex file. + // This does not check the current status before attempting to relocate the + // oat file. + // Returns true on success. + // This will fail if dex2oat is not enabled in the current runtime. + // + // 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 nullptr. + bool RelocateOatFile(std::string* error_msg); + + // Generate the oat file from the dex file. + // This does not check the current status before attempting to generate the + // oat file. + // Returns true on success. + // This will fail if dex2oat is not enabled in the current runtime. + // + // 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 nullptr. + bool GenerateOatFile(std::string* error_msg); + + // 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 nullptr. + // + // 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. + // Returns false on error, in which case error_msg describes the error. + // Neither odex_filename nor error_msg may be null. + static bool DexFilenameToOdexFilename(const std::string& location, + InstructionSet isa, std::string* odex_filename, std::string* error_msg); + + private: + struct ImageInfo { + uint32_t oat_checksum = 0; + uintptr_t oat_data_begin = 0; + int32_t patch_delta = 0; + std::string location; + }; + + // Returns the path to the dalvik cache directory. + // Does not check existence of the cache or try to create it. + // Includes the trailing slash. + // Returns an empty string if we can't get the dalvik cache directory path. + std::string DalvikCacheDirectory(); + + // Constructs the filename for the profile file. + // Returns an empty string if we do not have the necessary information to + // construct the filename. + std::string ProfileFileName(); + + // Constructs the filename for the old profile file. + // Returns an empty string if we do not have the necessary information to + // construct the filename. + std::string OldProfileFileName(); + + // 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 checksum required for an up-to-date oat file. + // Returns dex_checksum if a required checksum was located. Returns + // nullptr if the required checksum was not found. + // The caller shouldn't clean up or free the returned pointer. + const uint32_t* GetRequiredDexChecksum(); + + // Returns the loaded odex file. + // Loads the file if needed. Returns nullptr if the file failed to load. + // The caller shouldn't clean up or free the returned pointer. + const OatFile* GetOdexFile(); + + // Clear any cached information about the odex file that depends on the + // contents of the file. + void ClearOdexFileCache(); + + // Returns the loaded oat file. + // Loads the file if needed. Returns nullptr if the file failed to load. + // The caller shouldn't clean up or free the returned pointer. + const OatFile* GetOatFile(); + + // Clear any cached information about the oat file that depends on the + // contents of the file. + void ClearOatFileCache(); + + // Returns the loaded image info. + // Loads the image info if needed. Returns nullptr if the image info failed + // to load. + // The caller shouldn't clean up or free the returned pointer. + const ImageInfo* GetImageInfo(); + + // Returns the loaded profile. + // Loads the profile if needed. Returns nullptr if the profile failed + // to load. + // The caller shouldn't clean up or free the returned pointer. + ProfileFile* GetProfile(); + + // Returns the loaded old profile. + // Loads the old profile if needed. Returns nullptr if the old profile + // failed to load. + // The caller shouldn't clean up or free the returned pointer. + ProfileFile* GetOldProfile(); + + // To implement Lock(), we lock a dummy file where the oat file would go + // (adding ".flock" to the target file name) and retain the lock for the + // remaining lifetime of the OatFileAssistant object. + std::unique_ptr<File> lock_file_; + ScopedFlock flock_; + + // In a properly constructed OatFileAssistant object, dex_location_ should + // never be nullptr. + const char* dex_location_ = nullptr; + + // In a properly constructed OatFileAssistant object, isa_ should be either + // the 32 or 64 bit variant for the current device. + const InstructionSet isa_ = kNone; + + // The package name, used solely to find the profile file. + // This may be nullptr in a properly constructed object. In this case, + // profile_load_attempted_ and old_profile_load_attempted_ will be true, and + // profile_load_succeeded_ and old_profile_load_succeeded_ will be false. + const char* package_name_ = nullptr; + + // Whether we will attempt to load oat files executable. + bool load_executable_ = false; + + // Cached value of the required dex checksum. + // This should be accessed only by the GetRequiredDexChecksum() method. + uint32_t cached_required_dex_checksum; + bool required_dex_checksum_attempted = false; + bool required_dex_checksum_found; + + // Cached value of the odex file name. + // This should be accessed only by the OdexFileName() method. + bool cached_odex_file_name_attempted_ = false; + bool cached_odex_file_name_found_; + std::string cached_odex_file_name_; + + // Cached value of the loaded odex file. + // Use the GetOdexFile method rather than accessing this directly, unless you + // know the odex file isn't out of date. + bool odex_file_load_attempted_ = false; + std::unique_ptr<OatFile> cached_odex_file_; + + // Cached results for OdexFileIsOutOfDate + bool odex_file_is_out_of_date_attempted_ = false; + bool cached_odex_file_is_out_of_date_; + + // Cached results for OdexFileIsUpToDate + bool odex_file_is_up_to_date_attempted_ = false; + bool cached_odex_file_is_up_to_date_; + + // Cached value of the oat file name. + // This should be accessed only by the OatFileName() method. + bool cached_oat_file_name_attempted_ = false; + bool cached_oat_file_name_found_; + std::string cached_oat_file_name_; + + // Cached value of the loaded odex file. + // Use the GetOatFile method rather than accessing this directly, unless you + // know the odex file isn't out of date. + bool oat_file_load_attempted_ = false; + std::unique_ptr<OatFile> cached_oat_file_; + + // Cached results for OatFileIsOutOfDate + bool oat_file_is_out_of_date_attempted_ = false; + bool cached_oat_file_is_out_of_date_; + + // Cached results for OatFileIsUpToDate + bool oat_file_is_up_to_date_attempted_ = false; + bool cached_oat_file_is_up_to_date_; + + // Cached value of the image info. + // Use the GetImageInfo method rather than accessing these directly. + // TODO: The image info should probably be moved out of the oat file + // assistant to an image file manager. + bool image_info_load_attempted_ = false; + bool image_info_load_succeeded_ = false; + ImageInfo cached_image_info_; + + // Cached value of the profile file. + // Use the GetProfile method rather than accessing these directly. + bool profile_load_attempted_ = false; + bool profile_load_succeeded_ = false; + ProfileFile cached_profile_; + + // Cached value of the profile file. + // Use the GetOldProfile method rather than accessing these directly. + bool old_profile_load_attempted_ = false; + bool old_profile_load_succeeded_ = false; + ProfileFile cached_old_profile_; + + // For debugging only. + // If this flag is set, the oat or odex file has been released to the user + // of the OatFileAssistant object and the OatFileAssistant object is in a + // bad state and should no longer be used. + bool oat_file_released_ = false; + + DISALLOW_COPY_AND_ASSIGN(OatFileAssistant); +}; + +} // namespace art + +#endif // ART_RUNTIME_OAT_FILE_ASSISTANT_H_ diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc new file mode 100644 index 0000000000..d683cdc31c --- /dev/null +++ b/runtime/oat_file_assistant_test.cc @@ -0,0 +1,874 @@ +/* + * Copyright (C) 2014 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 "oat_file_assistant.h" + +#include <algorithm> +#include <fstream> +#include <string> +#include <vector> +#include <sys/param.h> + +#include <backtrace/BacktraceMap.h> +#include <gtest/gtest.h> + +#include "class_linker.h" +#include "common_runtime_test.h" +#include "mem_map.h" +#include "os.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +class OatFileAssistantTest : public CommonRuntimeTest { + public: + virtual void SetUp() { + ReserveImageSpace(); + CommonRuntimeTest::SetUp(); + + // Create a scratch directory to work from. + scratch_dir_ = android_data_ + "/OatFileAssistantTest"; + ASSERT_EQ(0, mkdir(scratch_dir_.c_str(), 0700)); + + // Create a subdirectory in scratch for the current isa. + // This is the location that will be used for odex files in the tests. + isa_dir_ = scratch_dir_ + "/" + GetInstructionSetString(kRuntimeISA); + ASSERT_EQ(0, mkdir(isa_dir_.c_str(), 0700)); + + // Verify the environment is as we expect + uint32_t checksum; + std::string error_msg; + ASSERT_TRUE(OS::FileExists(GetImageFile().c_str())) + << "Expected pre-compiled boot image to be at: " << GetImageFile(); + ASSERT_TRUE(OS::FileExists(GetDexSrc1().c_str())) + << "Expected dex file to be at: " << GetDexSrc1(); + ASSERT_TRUE(OS::FileExists(GetStrippedDexSrc1().c_str())) + << "Expected stripped dex file to be at: " << GetStrippedDexSrc1(); + ASSERT_FALSE(DexFile::GetChecksum(GetStrippedDexSrc1().c_str(), &checksum, &error_msg)) + << "Expected stripped dex file to be stripped: " << GetStrippedDexSrc1(); + ASSERT_TRUE(OS::FileExists(GetMultiDexSrc1().c_str())) + << "Expected multidex file to be at: " << GetMultiDexSrc1(); + ASSERT_TRUE(OS::FileExists(GetDexSrc2().c_str())) + << "Expected dex file to be at: " << GetDexSrc2(); + } + + virtual void SetUpRuntimeOptions(RuntimeOptions* options) { + // options->push_back(std::make_pair("-verbose:oat", nullptr)); + + // Set up the image location. + options->push_back(std::make_pair("-Ximage:" + GetImageLocation(), + nullptr)); + // Make sure compilercallbacks are not set so that relocation will be + // enabled. + for (std::pair<std::string, const void*>& pair : *options) { + if (pair.first == "compilercallbacks") { + pair.second = nullptr; + } + } + } + + virtual void PreRuntimeCreate() { + UnreserveImageSpace(); + } + + virtual void PostRuntimeCreate() { + ReserveImageSpace(); + } + + virtual void TearDown() { + ClearDirectory(isa_dir_.c_str()); + ASSERT_EQ(0, rmdir(isa_dir_.c_str())); + + ClearDirectory(scratch_dir_.c_str()); + ASSERT_EQ(0, rmdir(scratch_dir_.c_str())); + + CommonRuntimeTest::TearDown(); + } + + void Copy(std::string src, std::string dst) { + std::ifstream src_stream(src, std::ios::binary); + std::ofstream dst_stream(dst, std::ios::binary); + + dst_stream << src_stream.rdbuf(); + } + + // Returns the directory where the pre-compiled core.art can be found. + // TODO: We should factor out this into common tests somewhere rather than + // re-hardcoding it here (This was copied originally from the elf writer + // test). + std::string GetImageDirectory() { + if (IsHost()) { + const char* host_dir = getenv("ANDROID_HOST_OUT"); + CHECK(host_dir != NULL); + return std::string(host_dir) + "/framework"; + } else { + return std::string("/data/art-test"); + } + } + + std::string GetImageLocation() { + return GetImageDirectory() + "/core.art"; + } + + std::string GetImageFile() { + return GetImageDirectory() + "/" + GetInstructionSetString(kRuntimeISA) + + "/core.art"; + } + + std::string GetDexSrc1() { + return GetTestDexFileName("Main"); + } + + // Returns the path to a dex file equivalent to GetDexSrc1, but with the dex + // file stripped. + std::string GetStrippedDexSrc1() { + return GetTestDexFileName("MainStripped"); + } + + std::string GetMultiDexSrc1() { + return GetTestDexFileName("MultiDex"); + } + + std::string GetDexSrc2() { + return GetTestDexFileName("Nested"); + } + + // Scratch directory, for dex and odex files (oat files will go in the + // dalvik cache). + std::string GetScratchDir() { + return scratch_dir_; + } + + // ISA directory is the subdirectory in the scratch directory where odex + // files should be located. + std::string GetISADir() { + return isa_dir_; + } + + // Generate an odex file for the purposes of test. + // If pic is true, generates a PIC odex. + void GenerateOdexForTest(const std::string& dex_location, + const std::string& odex_location, + bool pic = false) { + // For this operation, we temporarily redirect the dalvik cache so dex2oat + // doesn't find the relocated image file. + std::string android_data_tmp = GetScratchDir() + "AndroidDataTmp"; + setenv("ANDROID_DATA", android_data_tmp.c_str(), 1); + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + if (pic) { + args.push_back("--compile-pic"); + } else { + args.push_back("--include-patch-information"); + } + args.push_back("--runtime-arg"); + args.push_back("-Xnorelocate"); + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + setenv("ANDROID_DATA", android_data_.c_str(), 1); + } + + void GeneratePicOdexForTest(const std::string& dex_location, + const std::string& odex_location) { + GenerateOdexForTest(dex_location, odex_location, true); + } + + private: + // Reserve memory around where the image will be loaded so other memory + // won't conflict when it comes time to load the image. + // This can be called with an already loaded image to reserve the space + // around it. + void ReserveImageSpace() { + MemMap::Init(); + + // Ensure a chunk of memory is reserved for the image space. + uintptr_t reservation_start = ART_BASE_ADDRESS + ART_BASE_ADDRESS_MIN_DELTA; + uintptr_t reservation_end = ART_BASE_ADDRESS + ART_BASE_ADDRESS_MAX_DELTA + + 100 * 1024 * 1024; + + std::string error_msg; + std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true)); + ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map"; + for (BacktraceMap::const_iterator it = map->begin(); + reservation_start < reservation_end && it != map->end(); ++it) { + if (it->end <= reservation_start) { + continue; + } + + if (it->start < reservation_start) { + reservation_start = std::min(reservation_end, it->end); + } + + image_reservation_.push_back(std::unique_ptr<MemMap>( + MemMap::MapAnonymous("image reservation", + reinterpret_cast<uint8_t*>(reservation_start), + std::min(it->start, reservation_end) - reservation_start, + PROT_NONE, false, false, &error_msg))); + ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg; + LOG(INFO) << "Reserved space for image " << + reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" << + reinterpret_cast<void*>(image_reservation_.back()->End()); + reservation_start = it->end; + } + } + + + // Unreserve any memory reserved by ReserveImageSpace. This should be called + // before the image is loaded. + void UnreserveImageSpace() { + image_reservation_.clear(); + } + + std::string scratch_dir_; + std::string isa_dir_; + std::vector<std::unique_ptr<MemMap>> image_reservation_; +}; + +class OatFileAssistantNoDex2OatTest : public OatFileAssistantTest { + public: + virtual void SetUpRuntimeOptions(RuntimeOptions* options) { + OatFileAssistantTest::SetUpRuntimeOptions(options); + options->push_back(std::make_pair("-Xnodex2oat", nullptr)); + } +}; + +// Generate an oat file for the purposes of test, as opposed to testing +// generation of oat files. +static void GenerateOatForTest(const char* dex_location) { + OatFileAssistant oat_file_assistant(dex_location, kRuntimeISA, false); + + std::string error_msg; + ASSERT_TRUE(oat_file_assistant.GenerateOatFile(&error_msg)) << error_msg; +} + +// Case: We have a DEX file, but no OAT file for it. +// Expect: The oat file status is kOutOfDate. +TEST_F(OatFileAssistantTest, DexNoOat) { + std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; + Copy(GetDexSrc1(), dex_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileNeedsRelocation()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.OdexFileStatus()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileNeedsRelocation()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.OatFileStatus()); +} + +// Case: We have no DEX file and no OAT file. +// Expect: Status is out of date. Loading should fail, but not crash. +TEST_F(OatFileAssistantTest, NoDexNoOat) { + std::string dex_location = GetScratchDir() + "/NoDexNoOat.jar"; + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + EXPECT_EQ(nullptr, oat_file.get()); +} + +// Case: We have a DEX file and up-to-date OAT file for it. +// Expect: The oat file status is kUpToDate. +TEST_F(OatFileAssistantTest, OatUpToDate) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str()); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + EXPECT_EQ(OatFileAssistant::kUpToDate, oat_file_assistant.GetStatus()); + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileNeedsRelocation()); + EXPECT_TRUE(oat_file_assistant.OatFileIsUpToDate()); + EXPECT_EQ(OatFileAssistant::kUpToDate, oat_file_assistant.OatFileStatus()); +} + +// Case: We have a MultiDEX file and up-to-date OAT file for it. +// Expect: The oat file status is kUpToDate. +TEST_F(OatFileAssistantTest, MultiDexOatUpToDate) { + std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar"; + Copy(GetMultiDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str()); + + // Verify we can load both dex files. + OatFileAssistant executable_oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + std::unique_ptr<OatFile> oat_file = executable_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 = executable_oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str()); + EXPECT_EQ(2u, dex_files.size()); +} + +// Case: We have a DEX file and out of date OAT file. +// Expect: The oat file status is kOutOfDate. +TEST_F(OatFileAssistantTest, OatOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatOutOfDate.jar"; + + // We create a dex, generate an oat for it, then overwrite the dex with a + // different dex to make the oat out of date. + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str()); + Copy(GetDexSrc2(), dex_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); +} + +// Case: We have a DEX file and an ODEX file, but no OAT file. +// Expect: The oat file status is kNeedsRelocation. +TEST_F(OatFileAssistantTest, DexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar"; + std::string odex_location = GetISADir() + "/DexOdexNoOat.odex"; + + // Create the dex and odex files + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Verify the status. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + EXPECT_EQ(OatFileAssistant::kNeedsRelocation, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OdexFileNeedsRelocation()); + EXPECT_EQ(OatFileAssistant::kNeedsRelocation, oat_file_assistant.OdexFileNeedsRelocation()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); +} + +// Case: We have a stripped DEX file and an ODEX file, but no OAT file. +// Expect: The oat file status is kNeedsRelocation. +TEST_F(OatFileAssistantTest, StrippedDexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexNoOat.jar"; + std::string odex_location = GetISADir() + "/StrippedDexOdexNoOat.odex"; + + // Create the dex and odex files + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Strip the dex file + Copy(GetStrippedDexSrc1(), dex_location); + + // Verify the status. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_EQ(OatFileAssistant::kNeedsRelocation, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + + // Make the oat file up to date. + std::string error_msg; + ASSERT_TRUE(oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; + + EXPECT_EQ(OatFileAssistant::kUpToDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_TRUE(oat_file_assistant.OatFileIsUpToDate()); + + // Verify we can load the dex files from it. + 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(1u, dex_files.size()); +} + +// Case: We have a stripped DEX file, an ODEX file, and an out of date OAT file. +// Expect: The oat file status is kNeedsRelocation. +TEST_F(OatFileAssistantTest, StrippedDexOdexOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexOat.jar"; + std::string odex_location = GetISADir() + "/StrippedDexOdexOat.odex"; + + // Create the oat file from a different dex file so it looks out of date. + Copy(GetDexSrc2(), dex_location); + GenerateOatForTest(dex_location.c_str()); + + // Create the odex file + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Strip the dex file. + Copy(GetStrippedDexSrc1(), dex_location); + + // Verify the status. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_EQ(OatFileAssistant::kNeedsRelocation, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_TRUE(oat_file_assistant.OdexFileNeedsRelocation()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + + // Make the oat file up to date. + std::string error_msg; + ASSERT_TRUE(oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; + + EXPECT_EQ(OatFileAssistant::kUpToDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_TRUE(oat_file_assistant.OdexFileNeedsRelocation()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileNeedsRelocation()); + EXPECT_TRUE(oat_file_assistant.OatFileIsUpToDate()); + + // Verify we can load the dex files from it. + 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(1u, dex_files.size()); +} + +// Case: We have a DEX file, an ODEX file and an OAT file, where the ODEX and +// OAT files both have patch delta of 0. +// Expect: It shouldn't crash. +TEST_F(OatFileAssistantTest, OdexOatOverlap) { + std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar"; + std::string odex_location = GetISADir() + "/OdexOatOverlap.odex"; + std::string oat_location = GetISADir() + "/OdexOatOverlap.oat"; + + // Create the dex and odex files + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Create the oat file by copying the odex so they are located in the same + // place in memory. + Copy(odex_location, oat_location); + + // Verify things don't go bad. + OatFileAssistant oat_file_assistant(dex_location.c_str(), + oat_location.c_str(), kRuntimeISA, true); + + EXPECT_EQ(OatFileAssistant::kNeedsRelocation, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + + // Things aren't relocated, so it should fall back to interpreted. + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + ASSERT_TRUE(oat_file.get() != nullptr); + EXPECT_FALSE(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(1u, dex_files.size()); +} + +// Case: We have a DEX file and a PIC ODEX file, but no OAT file. +// Expect: The oat file status is kUpToDate, because PIC needs no relocation. +TEST_F(OatFileAssistantTest, DexPicOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexPicOdexNoOat.jar"; + std::string odex_location = GetISADir() + "/DexPicOdexNoOat.odex"; + + // Create the dex and odex files + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location); + + // Verify the status. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + EXPECT_EQ(OatFileAssistant::kUpToDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_TRUE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); +} + +// Case: We have a DEX file and up-to-date OAT file for it. +// Expect: We should load an executable dex file. +TEST_F(OatFileAssistantTest, LoadOatUpToDate) { + std::string dex_location = GetScratchDir() + "/LoadOatUpToDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str()); + + // Load the oat using an oat file assistant. + 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(1u, dex_files.size()); +} + +// Case: We have a DEX file and up-to-date OAT file for it. +// Expect: Loading non-executable should load the oat non-executable. +TEST_F(OatFileAssistantTest, LoadNoExecOatUpToDate) { + std::string dex_location = GetScratchDir() + "/LoadNoExecOatUpToDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str()); + + // Load the oat using an oat file assistant. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + ASSERT_TRUE(oat_file.get() != nullptr); + EXPECT_FALSE(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(1u, dex_files.size()); +} + +// Case: We have a DEX file. +// Expect: We should load an executable dex file from an alternative oat +// location. +TEST_F(OatFileAssistantTest, LoadDexNoAlternateOat) { + std::string dex_location = GetScratchDir() + "/LoadDexNoAlternateOat.jar"; + std::string oat_location = GetScratchDir() + "/LoadDexNoAlternateOat.oat"; + + Copy(GetDexSrc1(), dex_location); + + OatFileAssistant oat_file_assistant( + dex_location.c_str(), oat_location.c_str(), kRuntimeISA, true); + std::string error_msg; + ASSERT_TRUE(oat_file_assistant.MakeUpToDate(&error_msg)) << error_msg; + + 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(1u, dex_files.size()); + + EXPECT_TRUE(OS::FileExists(oat_location.c_str())); + + // Verify it didn't create an oat in the default location. + OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false); + EXPECT_FALSE(ofm.OatFileExists()); +} + +// Case: Non-existent Dex location. +// Expect: The dex code is out of date, and trying to update it fails. +TEST_F(OatFileAssistantTest, NonExsistentDexLocation) { + std::string dex_location = GetScratchDir() + "/BadDexLocation.jar"; + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + + std::string error_msg; + EXPECT_FALSE(oat_file_assistant.MakeUpToDate(&error_msg)); + EXPECT_FALSE(error_msg.empty()); +} + +// Turn an absolute path into a path relative to the current working +// directory. +static std::string MakePathRelative(std::string target) { + char buf[MAXPATHLEN]; + std::string cwd = getcwd(buf, MAXPATHLEN); + + // Split the target and cwd paths into components. + std::vector<std::string> target_path; + std::vector<std::string> cwd_path; + Split(target, '/', &target_path); + Split(cwd, '/', &cwd_path); + + // Reverse the path components, so we can use pop_back(). + std::reverse(target_path.begin(), target_path.end()); + std::reverse(cwd_path.begin(), cwd_path.end()); + + // Drop the common prefix of the paths. Because we reversed the path + // components, this becomes the common suffix of target_path and cwd_path. + while (!target_path.empty() && !cwd_path.empty() + && target_path.back() == cwd_path.back()) { + target_path.pop_back(); + cwd_path.pop_back(); + } + + // For each element of the remaining cwd_path, add '..' to the beginning + // of the target path. Because we reversed the path components, we add to + // the end of target_path. + for (unsigned int i = 0; i < cwd_path.size(); i++) { + target_path.push_back(".."); + } + + // Reverse again to get the right path order, and join to get the result. + std::reverse(target_path.begin(), target_path.end()); + return Join(target_path, '/'); +} + +// Case: Non-absolute path to Dex location. +// Expect: Not sure, but it shouldn't crash. +TEST_F(OatFileAssistantTest, NonAbsoluteDexLocation) { + std::string abs_dex_location = GetScratchDir() + "/NonAbsoluteDexLocation.jar"; + Copy(GetDexSrc1(), abs_dex_location); + + std::string dex_location = MakePathRelative(abs_dex_location); + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); +} + +// Case: Very short, non-existent Dex location. +// Expect: Dex code is out of date, and trying to update it fails. +TEST_F(OatFileAssistantTest, ShortDexLocation) { + std::string dex_location = "/xx"; + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); + + std::string error_msg; + EXPECT_FALSE(oat_file_assistant.MakeUpToDate(&error_msg)); + EXPECT_FALSE(error_msg.empty()); +} + +// Case: Non-standard extension for dex file. +// Expect: The oat file status is kOutOfDate. +TEST_F(OatFileAssistantTest, LongDexExtension) { + std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx"; + Copy(GetDexSrc1(), dex_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + EXPECT_EQ(OatFileAssistant::kOutOfDate, oat_file_assistant.GetStatus()); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_FALSE(oat_file_assistant.OdexFileExists()); + EXPECT_TRUE(oat_file_assistant.OdexFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OdexFileIsUpToDate()); + EXPECT_FALSE(oat_file_assistant.OatFileExists()); + EXPECT_TRUE(oat_file_assistant.OatFileIsOutOfDate()); + EXPECT_FALSE(oat_file_assistant.OatFileIsUpToDate()); +} + +// 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) + {} + + void Run(Thread* self) { + UNUSED(self); + + // Load the dex files, and save a pointer to the loaded oat file, so that + // we can verify only one oat file was loaded for the dex location. + ClassLinker* linker = Runtime::Current()->GetClassLinker(); + std::vector<std::unique_ptr<const DexFile>> dex_files; + std::vector<std::string> error_msgs; + dex_files = linker->OpenDexFilesFromOat(dex_location_.c_str(), oat_location_.c_str(), &error_msgs); + CHECK(!dex_files.empty()) << Join(error_msgs, '\n'); + loaded_oat_file_ = dex_files[0]->GetOatFile(); + } + + const OatFile* GetLoadedOatFile() const { + return loaded_oat_file_; + } + + private: + std::string dex_location_; + std::string oat_location_; + 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 only one Oat file to be loaded when there is a race to load, to +// avoid using up the virtual memory address space. +TEST_F(OatFileAssistantTest, RaceToGenerate) { + std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar"; + std::string oat_location = GetISADir() + "/RaceToGenerate.oat"; + + // We use the lib core dex file, because it's large, and hopefully should + // take a while to generate. + Copy(GetLibCoreDexFileName(), dex_location); + + const int 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++) { + 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); + + // Verify every task got the same pointer. + const OatFile* expected = tasks[0]->GetLoadedOatFile(); + for (auto& task : tasks) { + EXPECT_EQ(expected, task->GetLoadedOatFile()); + } +} + +// Case: We have a DEX file and an ODEX file, no OAT file, and dex2oat is +// disabled. +// Expect: We should load the odex file non-executable. +TEST_F(OatFileAssistantNoDex2OatTest, LoadDexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/LoadDexOdexNoOat.jar"; + std::string odex_location = GetISADir() + "/LoadDexOdexNoOat.odex"; + + // Create the dex and odex files + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Load the oat using an executable oat file assistant. + 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_FALSE(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(1u, dex_files.size()); +} + +// Case: We have a MultiDEX file and an ODEX file, no OAT file, and dex2oat is +// disabled. +// Expect: We should load the odex file non-executable. +TEST_F(OatFileAssistantNoDex2OatTest, LoadMultiDexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/LoadMultiDexOdexNoOat.jar"; + std::string odex_location = GetISADir() + "/LoadMultiDexOdexNoOat.odex"; + + // Create the dex and odex files + Copy(GetMultiDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location); + + // Load the oat using an executable oat file assistant. + 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_FALSE(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(OatFileAssistantUtilsTest, DexFilenameToOdexFilename) { + std::string error_msg; + std::string odex_file; + + EXPECT_TRUE(OatFileAssistant::DexFilenameToOdexFilename( + "/foo/bar/baz.jar", kArm, &odex_file, &error_msg)) << error_msg; + EXPECT_EQ("/foo/bar/arm/baz.odex", odex_file); + + EXPECT_TRUE(OatFileAssistant::DexFilenameToOdexFilename( + "/foo/bar/baz.funnyext", kArm, &odex_file, &error_msg)) << error_msg; + EXPECT_EQ("/foo/bar/arm/baz.odex", odex_file); + + EXPECT_FALSE(OatFileAssistant::DexFilenameToOdexFilename( + "nopath.jar", kArm, &odex_file, &error_msg)); + EXPECT_FALSE(OatFileAssistant::DexFilenameToOdexFilename( + "/foo/bar/baz_noext", kArm, &odex_file, &error_msg)); +} + + +// TODO: More Tests: +// * Test class linker falls back to unquickened dex for DexNoOat +// * Test class linker falls back to unquickened dex for MultiDexNoOat +// * Test multidex files: +// - Multidex with only classes2.dex out of date should have status +// kOutOfDate +// * Test using secondary isa +// * Test with profiling info? +// * Test for status of oat while oat is being generated (how?) +// * Test case where 32 and 64 bit boot class paths differ, +// and we ask IsInBootClassPath for a class in exactly one of the 32 or +// 64 bit boot class paths. +// * Test unexpected scenarios (?): +// - Dex is stripped, don't have odex. +// - Oat file corrupted after status check, before reload unexecutable +// because it's unrelocated and no dex2oat + +} // namespace art diff --git a/runtime/utils.cc b/runtime/utils.cc index 851ecebb05..8a23ff7233 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -1508,23 +1508,6 @@ std::string GetSystemImageFilename(const char* location, const InstructionSet is return filename; } -std::string DexFilenameToOdexFilename(const std::string& location, const InstructionSet isa) { - // location = /foo/bar/baz.jar - // odex_location = /foo/bar/<isa>/baz.odex - std::string odex_location(location); - InsertIsaDirectory(isa, &odex_location); - size_t dot_index = odex_location.rfind('.'); - - // The location must have an extension, otherwise it's not clear what we - // should return. - CHECK_NE(dot_index, std::string::npos) << odex_location; - CHECK_EQ(std::string::npos, odex_location.find('/', dot_index)) << odex_location; - - odex_location.resize(dot_index + 1); - odex_location += "odex"; - return odex_location; -} - bool IsZipMagic(uint32_t magic) { return (('P' == ((magic >> 0) & 0xff)) && ('K' == ((magic >> 8) & 0xff))); diff --git a/runtime/utils.h b/runtime/utils.h index 9d04d35e26..d294f4b1a1 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -516,12 +516,6 @@ std::string GetDalvikCacheFilenameOrDie(const char* file_location, // Returns the system location for an image std::string GetSystemImageFilename(const char* location, InstructionSet isa); -// Returns an .odex file name adjacent to the dex location. -// For example, for "/foo/bar/baz.jar", return "/foo/bar/<isa>/baz.odex". -// The dex location must include a directory component and have an extension. -// Note: does not support multidex location strings. -std::string DexFilenameToOdexFilename(const std::string& location, InstructionSet isa); - // Check whether the given magic matches a known file type. bool IsZipMagic(uint32_t magic); bool IsDexMagic(uint32_t magic); diff --git a/runtime/utils_test.cc b/runtime/utils_test.cc index 5465762fd9..6b36c192e8 100644 --- a/runtime/utils_test.cc +++ b/runtime/utils_test.cc @@ -371,13 +371,6 @@ TEST_F(UtilsTest, GetSystemImageFilename) { GetSystemImageFilename("/system/framework/boot.art", kArm).c_str()); } -TEST_F(UtilsTest, DexFilenameToOdexFilename) { - EXPECT_STREQ("/foo/bar/arm/baz.odex", - DexFilenameToOdexFilename("/foo/bar/baz.jar", kArm).c_str()); - EXPECT_STREQ("/foo/bar/arm/baz.odex", - DexFilenameToOdexFilename("/foo/bar/baz.funnyext", kArm).c_str()); -} - TEST_F(UtilsTest, ExecSuccess) { std::vector<std::string> command; if (kIsTargetBuild) { diff --git a/test/MultiDex/Main.java b/test/MultiDex/Main.java new file mode 100644 index 0000000000..659dba9af6 --- /dev/null +++ b/test/MultiDex/Main.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 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. + */ + +class Main { + public static void main(String args[]) { + Second second = new Second(); + System.out.println(second.getSecond()); + } +} diff --git a/test/MultiDex/Second.java b/test/MultiDex/Second.java new file mode 100644 index 0000000000..540aedbb1a --- /dev/null +++ b/test/MultiDex/Second.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 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. + */ + +class Second { + public String getSecond() { + return "I Second That."; + } +} diff --git a/test/MultiDex/main.list b/test/MultiDex/main.list new file mode 100644 index 0000000000..44ba78ead5 --- /dev/null +++ b/test/MultiDex/main.list @@ -0,0 +1 @@ +Main.class |