Support to pass <uses-library> option through to dex2oat.
This change takes an app's shared libraries specified by <uses-library>
and passes it through to dex2oat to be used during compilation.
Part of a multi-project change.
Bug: 26880306
(cherry-picked from commit 26e8a2f150cd7f7195a10650ab8a5b6fa5014bc8)
Change-Id: I72a352abdfc37eacd8bedfa6c218e3809ca8e39c
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index e9b8643..1835c72 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1063,9 +1063,8 @@
return true;
}
-static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
- mirror::ClassLoader* class_loader)
- SHARED_REQUIRES(Locks::mutator_lock_) {
+bool ClassLinker::IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ mirror::ClassLoader* class_loader) {
return class_loader == nullptr ||
class_loader->GetClass() ==
soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_BootClassLoader);
@@ -1106,7 +1105,7 @@
soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements);
CHECK(dex_path_list_field != nullptr);
CHECK(dex_elements_field != nullptr);
- while (!IsBootClassLoader(soa, class_loader)) {
+ while (!ClassLinker::IsBootClassLoader(soa, class_loader)) {
if (class_loader->GetClass() !=
soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader)) {
*error_msg = StringPrintf("Unknown class loader type %s", PrettyTypeOf(class_loader).c_str());
@@ -7815,7 +7814,8 @@
return descriptor;
}
-jobject ClassLinker::CreatePathClassLoader(Thread* self, std::vector<const DexFile*>& dex_files) {
+jobject ClassLinker::CreatePathClassLoader(Thread* self,
+ const std::vector<const DexFile*>& dex_files) {
// SOAAlreadyRunnable is protected, and we need something to add a global reference.
// We could move the jobject to the callers, but all call-sites do this...
ScopedObjectAccessUnchecked soa(self);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 45c0767..f6ce545 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -560,7 +560,7 @@
// Creates a GlobalRef PathClassLoader that can be used to load classes from the given dex files.
// Note: the objects are not completely set up. Do not use this outside of tests and the compiler.
- jobject CreatePathClassLoader(Thread* self, std::vector<const DexFile*>& dex_files)
+ jobject CreatePathClassLoader(Thread* self, const std::vector<const DexFile*>& dex_files)
SHARED_REQUIRES(Locks::mutator_lock_)
REQUIRES(!dex_lock_);
@@ -611,6 +611,10 @@
const std::set<DexCacheResolvedClasses>& classes)
REQUIRES(!dex_lock_);
+ static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ mirror::ClassLoader* class_loader)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
ArtMethod* AddMethodToConflictTable(mirror::Class* klass,
ArtMethod* conflict_method,
ArtMethod* interface_method,
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index fa540c0..cdd5f2e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -311,7 +311,7 @@
const OatHeader& boot_oat_header = boot_oat_file->GetOatHeader();
const char* boot_classpath =
- boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+ boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
if (boot_classpath == nullptr) {
continue;
}
diff --git a/runtime/oat.h b/runtime/oat.h
index 543d99f..57675dc 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -43,7 +43,7 @@
static constexpr const char* kNativeDebuggableKey = "native-debuggable";
static constexpr const char* kCompilerFilter = "compiler-filter";
static constexpr const char* kClassPathKey = "classpath";
- static constexpr const char* kBootClassPath = "bootclasspath";
+ static constexpr const char* kBootClassPathKey = "bootclasspath";
static constexpr const char kTrueValue[] = "true";
static constexpr const char kFalseValue[] = "false";
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 9470624..aa727ff 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -48,6 +48,9 @@
class OatFile {
public:
+ // Special classpath that skips shared library check.
+ static constexpr const char* kSpecialSharedLibrary = "&";
+
typedef art::OatDexFile OatDexFile;
// Opens an oat file contained within the given elf file. This is always opened as
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 713e2f3..fba10ca 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -771,7 +771,11 @@
argv.push_back("--runtime-arg");
argv.push_back("-classpath");
argv.push_back("--runtime-arg");
- argv.push_back(runtime->GetClassPathString());
+ std::string class_path = runtime->GetClassPathString();
+ if (class_path == "") {
+ class_path = OatFile::kSpecialSharedLibrary;
+ }
+ argv.push_back(class_path);
if (runtime->IsDebuggable()) {
argv.push_back("--debuggable");
}
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 764b969..15a1aa4 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -1264,8 +1264,7 @@
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)
+ : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr)
{}
void Run(Thread* self ATTRIBUTE_UNUSED) {
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index bc01da4..0af6716 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -36,11 +36,6 @@
namespace art {
-// For b/21333911.
-// Only enabled for debug builds to prevent bit rot. There are too many performance regressions for
-// normal builds.
-static constexpr bool kDuplicateClassesCheck = kIsDebugBuild;
-
// If true, then we attempt to load the application image if it exists.
static constexpr bool kEnableAppImage = true;
@@ -173,7 +168,7 @@
void Next() {
++current_class_index_;
- cached_descriptor_ = GetClassDescriptor(dex_file_.get(), current_class_index_);
+ cached_descriptor_ = GetClassDescriptor(dex_file_, current_class_index_);
}
size_t GetCurrentClassIndex() const {
@@ -185,7 +180,12 @@
}
const DexFile* GetDexFile() const {
- return dex_file_.get();
+ return dex_file_;
+ }
+
+ void DeleteDexFile() {
+ delete dex_file_;
+ dex_file_ = nullptr;
}
private:
@@ -196,7 +196,7 @@
}
const char* cached_descriptor_;
- std::shared_ptr<const DexFile> dex_file_;
+ const DexFile* dex_file_;
size_t current_class_index_;
bool from_loaded_oat_; // We only need to compare mismatches between what we load now
// and what was loaded before. Any old duplicates must have been
@@ -219,53 +219,299 @@
}
static void AddNext(/*inout*/DexFileAndClassPair* original,
- /*inout*/std::priority_queue<DexFileAndClassPair>* heap) {
+ /*inout*/std::priority_queue<DexFileAndClassPair>* heap,
+ bool owning_dex_files) {
if (original->DexFileHasMoreClasses()) {
original->Next();
heap->push(std::move(*original));
+ } else if (owning_dex_files) {
+ original->DeleteDexFile();
}
}
+static void FreeDexFilesInHeap(std::priority_queue<DexFileAndClassPair>* heap,
+ bool owning_dex_files) {
+ if (owning_dex_files) {
+ while (!heap->empty()) {
+ delete heap->top().GetDexFile();
+ heap->pop();
+ }
+ }
+}
+
+static void IterateOverJavaDexFile(mirror::Object* dex_file,
+ ArtField* const cookie_field,
+ std::function<bool(const DexFile*)> fn)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (dex_file != nullptr) {
+ mirror::LongArray* long_array = cookie_field->GetObject(dex_file)->AsLongArray();
+ if (long_array == nullptr) {
+ // This should never happen so log a warning.
+ LOG(WARNING) << "Null DexFile::mCookie";
+ return;
+ }
+ int32_t long_array_size = long_array->GetLength();
+ // Start from 1 to skip the oat file.
+ for (int32_t j = 1; j < long_array_size; ++j) {
+ const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
+ long_array->GetWithoutChecks(j)));
+ if (!fn(cp_dex_file)) {
+ return;
+ }
+ }
+ }
+}
+
+static void IterateOverPathClassLoader(
+ ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader,
+ MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements,
+ std::function<bool(const DexFile*)> fn) SHARED_REQUIRES(Locks::mutator_lock_) {
+ // Handle this step.
+ // Handle as if this is the child PathClassLoader.
+ // The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
+ // We need to get the DexPathList and loop through it.
+ ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie);
+ ArtField* const dex_file_field =
+ soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+ mirror::Object* dex_path_list =
+ soa.DecodeField(WellKnownClasses::dalvik_system_PathClassLoader_pathList)->
+ GetObject(class_loader.Get());
+ if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
+ // DexPathList has an array dexElements of Elements[] which each contain a dex file.
+ mirror::Object* dex_elements_obj =
+ soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+ GetObject(dex_path_list);
+ // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
+ // at the mCookie which is a DexFile vector.
+ if (dex_elements_obj != nullptr) {
+ dex_elements.Assign(dex_elements_obj->AsObjectArray<mirror::Object>());
+ for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+ mirror::Object* element = dex_elements->GetWithoutChecks(i);
+ if (element == nullptr) {
+ // Should never happen, fall back to java code to throw a NPE.
+ break;
+ }
+ mirror::Object* dex_file = dex_file_field->GetObject(element);
+ IterateOverJavaDexFile(dex_file, cookie_field, fn);
+ }
+ }
+ }
+}
+
+static bool GetDexFilesFromClassLoader(
+ ScopedObjectAccessAlreadyRunnable& soa,
+ mirror::ClassLoader* class_loader,
+ std::priority_queue<DexFileAndClassPair>* queue) SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (ClassLinker::IsBootClassLoader(soa, class_loader)) {
+ // The boot class loader. We don't load any of these files, as we know we compiled against
+ // them correctly.
+ return true;
+ }
+
+ // Unsupported class-loader?
+ if (class_loader->GetClass() !=
+ soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader)) {
+ VLOG(class_linker) << "Unsupported class-loader " << PrettyClass(class_loader->GetClass());
+ return false;
+ }
+
+ bool recursive_result = GetDexFilesFromClassLoader(soa, class_loader->GetParent(), queue);
+ if (!recursive_result) {
+ // Something wrong up the chain.
+ return false;
+ }
+
+ // Collect all the dex files.
+ auto GetDexFilesFn = [&] (const DexFile* cp_dex_file)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (cp_dex_file->NumClassDefs() > 0) {
+ queue->emplace(cp_dex_file, 0U, true);
+ }
+ return true; // Continue looking.
+ };
+
+ // Handle for dex-cache-element.
+ StackHandleScope<3> hs(soa.Self());
+ MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements(
+ hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr));
+ Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(class_loader));
+
+ IterateOverPathClassLoader(soa, h_class_loader, dex_elements, GetDexFilesFn);
+
+ return true;
+}
+
+static void GetDexFilesFromDexElementsArray(
+ ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
+ std::priority_queue<DexFileAndClassPair>* queue) SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (dex_elements.Get() == nullptr) {
+ // Nothing to do.
+ return;
+ }
+
+ ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie);
+ ArtField* const dex_file_field =
+ soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+ const mirror::Class* const element_class = soa.Decode<mirror::Class*>(
+ WellKnownClasses::dalvik_system_DexPathList__Element);
+ const mirror::Class* const dexfile_class = soa.Decode<mirror::Class*>(
+ WellKnownClasses::dalvik_system_DexFile);
+
+ // Collect all the dex files.
+ auto GetDexFilesFn = [&] (const DexFile* cp_dex_file)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (cp_dex_file != nullptr && cp_dex_file->NumClassDefs() > 0) {
+ queue->emplace(cp_dex_file, 0U, true);
+ }
+ return true; // Continue looking.
+ };
+
+ for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+ mirror::Object* element = dex_elements->GetWithoutChecks(i);
+ if (element == nullptr) {
+ continue;
+ }
+
+ // We support this being dalvik.system.DexPathList$Element and dalvik.system.DexFile.
+
+ mirror::Object* dex_file;
+ if (element->GetClass() == element_class) {
+ dex_file = dex_file_field->GetObject(element);
+ } else if (element->GetClass() == dexfile_class) {
+ dex_file = element;
+ } else {
+ LOG(WARNING) << "Unsupported element in dex_elements: " << PrettyClass(element->GetClass());
+ continue;
+ }
+
+ IterateOverJavaDexFile(dex_file, cookie_field, GetDexFilesFn);
+ }
+}
+
+static bool AreSharedLibrariesOk(const std::string shared_libraries,
+ std::priority_queue<DexFileAndClassPair>& queue) {
+ if (shared_libraries.empty()) {
+ if (queue.empty()) {
+ // No shared libraries or oat files, as expected.
+ return true;
+ }
+ } else {
+ if (shared_libraries.compare(OatFile::kSpecialSharedLibrary) == 0) {
+ // If we find the special shared library, skip the shared libraries check.
+ return true;
+ }
+ // Shared libraries is a series of dex file paths and their checksums, each separated by '*'.
+ std::vector<std::string> shared_libraries_split;
+ Split(shared_libraries, '*', &shared_libraries_split);
+
+ size_t index = 0;
+ std::priority_queue<DexFileAndClassPair> temp = queue;
+ while (!temp.empty() && index < shared_libraries_split.size() - 1) {
+ DexFileAndClassPair pair(temp.top());
+ const DexFile* dex_file = pair.GetDexFile();
+ std::string dex_filename(dex_file->GetLocation());
+ uint32_t dex_checksum = dex_file->GetLocationChecksum();
+ if (dex_filename != shared_libraries_split[index] ||
+ dex_checksum != std::stoul(shared_libraries_split[index + 1])) {
+ break;
+ }
+ temp.pop();
+ index += 2;
+ }
+
+ // Check is successful if it made it through the queue and all the shared libraries.
+ return temp.empty() && index == shared_libraries_split.size();
+ }
+ return false;
+}
+
// Check for class-def collisions in dex files.
//
-// This works by maintaining a heap with one class from each dex file, sorted by the class
-// descriptor. Then a dex-file/class pair is continually removed from the heap and compared
+// This first walks the class loader chain, getting all the dex files from the class loader. If
+// the class loader is null or one of the class loaders in the chain is unsupported, we collect
+// dex files from all open non-boot oat files to be safe.
+//
+// This first checks whether the shared libraries are in the expected order and the oat files
+// have the expected checksums. If so, we exit early. Otherwise, we do the collision check.
+//
+// The collision check works by maintaining a heap with one class from each dex file, sorted by the
+// class descriptor. Then a dex-file/class pair is continually removed from the heap and compared
// against the following top element. If the descriptor is the same, it is now checked whether
// the two elements agree on whether their dex file was from an already-loaded oat-file or the
// new oat file. Any disagreement indicates a collision.
bool OatFileManager::HasCollisions(const OatFile* oat_file,
+ jobject class_loader,
+ jobjectArray dex_elements,
std::string* error_msg /*out*/) const {
DCHECK(oat_file != nullptr);
DCHECK(error_msg != nullptr);
- if (!kDuplicateClassesCheck) {
- return false;
+
+ std::priority_queue<DexFileAndClassPair> queue;
+ bool owning_dex_files = false;
+
+ // Try to get dex files from the given class loader. If the class loader is null, or we do
+ // not support one of the class loaders in the chain, conservatively compare against all
+ // (non-boot) oat files.
+ bool class_loader_ok = false;
+ {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<2> hs(Thread::Current());
+ Handle<mirror::ClassLoader> h_class_loader =
+ hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader));
+ Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
+ hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>*>(dex_elements));
+ if (h_class_loader.Get() != nullptr &&
+ GetDexFilesFromClassLoader(soa, h_class_loader.Get(), &queue)) {
+ class_loader_ok = true;
+
+ // In this case, also take into account the dex_elements array, if given. We don't need to
+ // read it otherwise, as we'll compare against all open oat files anyways.
+ GetDexFilesFromDexElementsArray(soa, h_dex_elements, &queue);
+ } else if (h_class_loader.Get() != nullptr) {
+ VLOG(class_linker) << "Something unsupported with "
+ << PrettyClass(h_class_loader->GetClass());
+ }
}
// Dex files are registered late - once a class is actually being loaded. We have to compare
// against the open oat files. Take the oat_file_manager_lock_ that protects oat_files_ accesses.
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
- std::priority_queue<DexFileAndClassPair> queue;
+ if (!class_loader_ok) {
+ // Add dex files from already loaded oat files, but skip boot.
- // Add dex files from already loaded oat files, but skip boot.
- std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
- // The same OatFile can be loaded multiple times at different addresses. In this case, we don't
- // need to check both against each other since they would have resolved the same way at compile
- // time.
- std::unordered_set<std::string> unique_locations;
- for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) {
- DCHECK_NE(loaded_oat_file.get(), oat_file);
- const std::string& location = loaded_oat_file->GetLocation();
- if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) ==
- boot_oat_files.end() && location != oat_file->GetLocation() &&
- unique_locations.find(location) == unique_locations.end()) {
- unique_locations.insert(location);
- AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue);
+ // Clean up the queue.
+ while (!queue.empty()) {
+ queue.pop();
+ }
+
+ // Anything we load now is something we own and must be released later.
+ owning_dex_files = true;
+
+ std::vector<const OatFile*> boot_oat_files = GetBootOatFiles();
+ // The same OatFile can be loaded multiple times at different addresses. In this case, we don't
+ // need to check both against each other since they would have resolved the same way at compile
+ // time.
+ std::unordered_set<std::string> unique_locations;
+ for (const std::unique_ptr<const OatFile>& loaded_oat_file : oat_files_) {
+ DCHECK_NE(loaded_oat_file.get(), oat_file);
+ const std::string& location = loaded_oat_file->GetLocation();
+ if (std::find(boot_oat_files.begin(), boot_oat_files.end(), loaded_oat_file.get()) ==
+ boot_oat_files.end() && location != oat_file->GetLocation() &&
+ unique_locations.find(location) == unique_locations.end()) {
+ unique_locations.insert(location);
+ AddDexFilesFromOat(loaded_oat_file.get(), /*already_loaded*/true, &queue);
+ }
}
}
- if (queue.empty()) {
- // No other oat files, return early.
+ // Exit if shared libraries are ok. Do a full duplicate classes check otherwise.
+ const std::string
+ shared_libraries(oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
+ if (AreSharedLibrariesOk(shared_libraries, queue)) {
+ FreeDexFilesInHeap(&queue, owning_dex_files);
return false;
}
@@ -290,16 +536,17 @@
compare_pop.GetCachedDescriptor(),
compare_pop.GetDexFile()->GetLocation().c_str(),
top.GetDexFile()->GetLocation().c_str());
+ FreeDexFilesInHeap(&queue, owning_dex_files);
return true;
}
queue.pop();
- AddNext(&top, &queue);
+ AddNext(&top, &queue, owning_dex_files);
} else {
// Something else. Done here.
break;
}
}
- AddNext(&compare_pop, &queue);
+ AddNext(&compare_pop, &queue, owning_dex_files);
}
return false;
@@ -363,7 +610,8 @@
if (oat_file != nullptr) {
// Take the file only if it has no collisions, or we must take it because of preopting.
- bool accept_oat_file = !HasCollisions(oat_file.get(), /*out*/ &error_msg);
+ bool accept_oat_file =
+ !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg);
if (!accept_oat_file) {
// Failed the collision check. Print warning.
if (Runtime::Current()->IsDexFileFallbackEnabled()) {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 7017dfc..a1d1275 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -116,9 +116,16 @@
void DumpForSigQuit(std::ostream& os);
private:
- // Check for duplicate class definitions of the given oat file against all open oat files.
+ // Check that the shared libraries in the given oat file match those in the given class loader and
+ // dex elements. If the class loader is null or we do not support one of the class loaders in the
+ // chain, compare against all non-boot oat files instead. If the shared libraries are not ok,
+ // check for duplicate class definitions of the given oat file against the oat files (either from
+ // the class loader and dex elements if possible or all non-boot oat files otherwise).
// Return true if there are any class definition collisions in the oat_file.
- bool HasCollisions(const OatFile* oat_file, /*out*/std::string* error_msg) const
+ bool HasCollisions(const OatFile* oat_file,
+ jobject class_loader,
+ jobjectArray dex_elements,
+ /*out*/ std::string* error_msg) const
REQUIRES(!Locks::oat_file_manager_lock_);
const OatFile* FindOpenedOatFileFromOatLocationLocked(const std::string& oat_location) const
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index ca8f8bb..63976d0 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -856,7 +856,7 @@
if (index == 0) {
// First file. See if this is a multi-image environment, and if so, enqueue the other images.
const OatHeader& boot_oat_header = oat_file->GetOatHeader();
- const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPath);
+ const char* boot_cp = boot_oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
if (boot_cp != nullptr) {
gc::space::ImageSpace::CreateMultiImageLocations(image_locations[0],
boot_cp,