Preverify InMemoryDexClassLoader-loaded classes from vdex
This patch creates a new subclass of OatFile - OatFileBackedByVdex -
which initializes OatDexClass instances using the verification info in
a vdex file created by a previous instance of the class loader.
The OatFile is not backed by an actual .oat file.
Bug: 72131483
Test: art/tools/run-libcore-tests.sh
Test: art/test.py -b -r -t 692 -t 693
Change-Id: I3fd055abe17ee9739c07f2e2f4fc2543e4ec8c9e
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index ae1eea5..eee8cfc 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -165,6 +165,37 @@
void operator=(const NullableScopedUtfChars&);
};
+static jobject CreateCookieFromOatFileManagerResult(
+ JNIEnv* env,
+ std::vector<std::unique_ptr<const DexFile>>& dex_files,
+ const OatFile* oat_file,
+ const std::vector<std::string>& error_msgs) {
+ ClassLinker* linker = Runtime::Current()->GetClassLinker();
+ if (dex_files.empty()) {
+ ScopedObjectAccess soa(env);
+ CHECK(!error_msgs.empty());
+ // The most important message is at the end. So set up nesting by going forward, which will
+ // wrap the existing exception as a cause for the following one.
+ auto it = error_msgs.begin();
+ auto itEnd = error_msgs.end();
+ for ( ; it != itEnd; ++it) {
+ ThrowWrappedIOException("%s", it->c_str());
+ }
+ return nullptr;
+ }
+
+ jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
+ if (array == nullptr) {
+ ScopedObjectAccess soa(env);
+ for (auto& dex_file : dex_files) {
+ if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
+ dex_file.release(); // NOLINT
+ }
+ }
+ }
+ return array;
+}
+
static MemMap AllocateDexMemoryMap(JNIEnv* env, jint start, jint end) {
if (end <= start) {
ScopedObjectAccess soa(env);
@@ -187,33 +218,6 @@
return dex_mem_map;
}
-static const DexFile* CreateDexFile(JNIEnv* env, MemMap&& dex_mem_map) {
- std::string location = StringPrintf("Anonymous-DexFile@%p-%p",
- dex_mem_map.Begin(),
- dex_mem_map.End());
- std::string error_message;
- const ArtDexFileLoader dex_file_loader;
- std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(location,
- 0,
- std::move(dex_mem_map),
- /* verify= */ true,
- /* verify_checksum= */ true,
- &error_message));
- if (dex_file == nullptr) {
- ScopedObjectAccess soa(env);
- ThrowWrappedIOException("%s", error_message.c_str());
- return nullptr;
- }
-
- if (!dex_file->DisableWrite()) {
- ScopedObjectAccess soa(env);
- ThrowWrappedIOException("Failed to make dex file read-only");
- return nullptr;
- }
-
- return dex_file.release();
-}
-
struct ScopedIntArrayAccessor {
public:
ScopedIntArrayAccessor(JNIEnv* env, jintArray arr) : env_(env), array_(arr) {
@@ -238,7 +242,9 @@
jobjectArray buffers,
jobjectArray arrays,
jintArray jstarts,
- jintArray jends) {
+ jintArray jends,
+ jobject class_loader,
+ jobjectArray dex_elements) {
jsize buffers_length = env->GetArrayLength(buffers);
CHECK_EQ(buffers_length, env->GetArrayLength(arrays));
CHECK_EQ(buffers_length, env->GetArrayLength(jstarts));
@@ -248,8 +254,8 @@
ScopedIntArrayAccessor ends(env, jends);
// Allocate memory for dex files and copy data from ByteBuffers.
- std::vector<std::unique_ptr<const DexFile>> dex_files;
- dex_files.reserve(buffers_length);
+ std::vector<MemMap> dex_mem_maps;
+ dex_mem_maps.reserve(buffers_length);
for (jsize i = 0; i < buffers_length; ++i) {
jobject buffer = env->GetObjectArrayElement(buffers, i);
jbyteArray array = reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(arrays, i));
@@ -278,15 +284,20 @@
env->GetByteArrayRegion(array, start, end - start, destination);
}
- std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(dex_data)));
- if (dex_file == nullptr) {
- DCHECK(env->ExceptionCheck());
- return nullptr;
- }
- dex_files.push_back(std::move(dex_file));
+ dex_mem_maps.push_back(std::move(dex_data));
}
- return ConvertDexFilesToJavaArray(env, /* oat_file= */ nullptr, dex_files);
+ // Hand MemMaps over to OatFileManager to open the dex files and potentially
+ // create a backing OatFile instance from an anonymous vdex.
+ std::vector<std::string> error_msgs;
+ const OatFile* oat_file = nullptr;
+ std::vector<std::unique_ptr<const DexFile>> dex_files =
+ Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),
+ class_loader,
+ dex_elements,
+ /*out*/ &oat_file,
+ /*out*/ &error_msgs);
+ return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}
// TODO(calin): clean up the unused parameters (here and in libcore).
@@ -302,42 +313,15 @@
return nullptr;
}
- Runtime* const runtime = Runtime::Current();
- ClassLinker* linker = runtime->GetClassLinker();
- std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
-
- dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
- class_loader,
- dex_elements,
- /*out*/ &oat_file,
- /*out*/ &error_msgs);
-
- if (!dex_files.empty()) {
- jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
- if (array == nullptr) {
- ScopedObjectAccess soa(env);
- for (auto& dex_file : dex_files) {
- if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
- dex_file.release(); // NOLINT
- }
- }
- }
- return array;
- } else {
- ScopedObjectAccess soa(env);
- CHECK(!error_msgs.empty());
- // The most important message is at the end. So set up nesting by going forward, which will
- // wrap the existing exception as a cause for the following one.
- auto it = error_msgs.begin();
- auto itEnd = error_msgs.end();
- for ( ; it != itEnd; ++it) {
- ThrowWrappedIOException("%s", it->c_str());
- }
-
- return nullptr;
- }
+ std::vector<std::unique_ptr<const DexFile>> dex_files =
+ Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
+ class_loader,
+ dex_elements,
+ /*out*/ &oat_file,
+ /*out*/ &error_msgs);
+ return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}
static jstring DexFile_getClassLoaderContext(JNIEnv* env,
@@ -938,6 +922,8 @@
"[[B"
"[I"
"[I"
+ "Ljava/lang/ClassLoader;"
+ "[Ldalvik/system/DexPathList$Element;"
")Ljava/lang/Object;"),
NATIVE_METHOD(DexFile, getClassLoaderContext,
"(Ljava/lang/ClassLoader;"
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 98a09e1..624a1de 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -36,6 +36,7 @@
#include <android-base/logging.h>
#include "android-base/stringprintf.h"
+#include "arch/instruction_set_features.h"
#include "art_method.h"
#include "base/bit_vector.h"
#include "base/enums.h"
@@ -67,6 +68,7 @@
#include "oat_file_manager.h"
#include "runtime-inl.h"
#include "vdex_file.h"
+#include "verifier/verifier_deps.h"
namespace art {
@@ -163,6 +165,7 @@
virtual void PreSetup(const std::string& elf_filename) = 0;
bool Setup(int zip_fd, const char* abs_dex_location, std::string* error_msg);
+ bool Setup(const std::vector<const DexFile*>& dex_files);
// Setters exposed for ElfOatFile.
@@ -448,6 +451,27 @@
return true;
}
+bool OatFileBase::Setup(const std::vector<const DexFile*>& dex_files) {
+ for (const DexFile* dex_file : dex_files) {
+ std::string dex_location = dex_file->GetLocation();
+ std::string canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location.c_str());
+
+ // Create an OatDexFile and add it to the owning container.
+ OatDexFile* oat_dex_file = new OatDexFile(this, dex_file, dex_location, canonical_location);
+ oat_dex_files_storage_.push_back(oat_dex_file);
+
+ // Add the location and canonical location (if different) to the oat_dex_files_ table.
+ std::string_view key(oat_dex_file->GetDexFileLocation());
+ oat_dex_files_.Put(key, oat_dex_file);
+ if (canonical_location != dex_location) {
+ std::string_view canonical_key(oat_dex_file->GetCanonicalDexFileLocation());
+ oat_dex_files_.Put(canonical_key, oat_dex_file);
+ }
+ }
+
+ return true;
+}
+
bool OatFileBase::Setup(int zip_fd, const char* abs_dex_location, std::string* error_msg) {
if (!GetOatHeader().IsValid()) {
std::string cause = GetOatHeader().GetValidationErrorMessage();
@@ -1375,6 +1399,94 @@
return loaded;
}
+class OatFileBackedByVdex final : public OatFileBase {
+ public:
+ explicit OatFileBackedByVdex(const std::string& filename)
+ : OatFileBase(filename, /*executable=*/ false) {}
+
+ static OatFileBackedByVdex* Open(const std::vector<const DexFile*>& dex_files,
+ std::unique_ptr<VdexFile>&& vdex_file,
+ const std::string& location) {
+ std::unique_ptr<OatFileBackedByVdex> oat_file(new OatFileBackedByVdex(location));
+ oat_file->Initialize(dex_files, std::move(vdex_file));
+ return oat_file.release();
+ }
+
+ void Initialize(const std::vector<const DexFile*>& dex_files,
+ std::unique_ptr<VdexFile>&& vdex_file) {
+ DCHECK(!IsExecutable());
+
+ // SetVdex will take ownership of the VdexFile.
+ SetVdex(vdex_file.release());
+
+ // Create a dummy OatHeader.
+ std::unique_ptr<const InstructionSetFeatures> isa_features =
+ InstructionSetFeatures::FromCppDefines();
+ oat_header_.reset(OatHeader::Create(kRuntimeISA,
+ isa_features.get(),
+ dex_files.size(),
+ nullptr));
+ const uint8_t* begin = reinterpret_cast<const uint8_t*>(oat_header_.get());
+ SetBegin(begin);
+ SetEnd(begin + oat_header_->GetHeaderSize());
+
+ // Load VerifierDeps from VDEX and copy bit vectors of verified classes.
+ ArrayRef<const uint8_t> deps_data = GetVdexFile()->GetVerifierDepsData();
+ verified_classes_per_dex_ = verifier::VerifierDeps::ParseVerifiedClasses(dex_files, deps_data);
+
+ // Initialize OatDexFiles.
+ Setup(dex_files);
+ }
+
+ bool IsClassVerifiedInVdex(const OatDexFile& oat_dex_file, uint16_t class_def_index) const {
+ // Determine the index of the DexFile, assuming the order of OatDexFiles
+ // in `oat_dex_files_storage_` is the same.
+ const std::vector<const OatDexFile*>& oat_dex_files = GetOatDexFiles();
+ auto oat_dex_file_it = std::find(oat_dex_files.begin(), oat_dex_files.end(), &oat_dex_file);
+ DCHECK(oat_dex_file_it != oat_dex_files.end());
+ size_t dex_index = oat_dex_file_it - oat_dex_files.begin();
+ // Check the bitvector of verified classes from the vdex.
+ return verified_classes_per_dex_[dex_index][class_def_index];
+ }
+
+ protected:
+ void PreLoad() override {}
+
+ bool Load(const std::string& elf_filename ATTRIBUTE_UNUSED,
+ bool writable ATTRIBUTE_UNUSED,
+ bool executable ATTRIBUTE_UNUSED,
+ bool low_4gb ATTRIBUTE_UNUSED,
+ MemMap* reservation ATTRIBUTE_UNUSED,
+ std::string* error_msg ATTRIBUTE_UNUSED) override {
+ LOG(FATAL) << "Unsupported";
+ UNREACHABLE();
+ }
+
+ bool Load(int oat_fd ATTRIBUTE_UNUSED,
+ bool writable ATTRIBUTE_UNUSED,
+ bool executable ATTRIBUTE_UNUSED,
+ bool low_4gb ATTRIBUTE_UNUSED,
+ MemMap* reservation ATTRIBUTE_UNUSED,
+ std::string* error_msg ATTRIBUTE_UNUSED) override {
+ LOG(FATAL) << "Unsupported";
+ UNREACHABLE();
+ }
+
+ void PreSetup(const std::string& elf_filename ATTRIBUTE_UNUSED) override {}
+
+ const uint8_t* FindDynamicSymbolAddress(const std::string& symbol_name ATTRIBUTE_UNUSED,
+ std::string* error_msg) const override {
+ *error_msg = "Unsupported";
+ return nullptr;
+ }
+
+ private:
+ std::unique_ptr<OatHeader> oat_header_;
+ std::vector<std::vector<bool>> verified_classes_per_dex_;
+
+ DISALLOW_COPY_AND_ASSIGN(OatFileBackedByVdex);
+};
+
//////////////////////////
// General OatFile code //
//////////////////////////
@@ -1548,6 +1660,13 @@
error_msg);
}
+OatFile* OatFile::OpenFromVdex(const std::vector<const DexFile*>& dex_files,
+ std::unique_ptr<VdexFile>&& vdex_file,
+ const std::string& location) {
+ CheckLocation(location);
+ return OatFileBackedByVdex::Open(dex_files, std::move(vdex_file), location);
+}
+
OatFile::OatFile(const std::string& location, bool is_executable)
: location_(location),
vdex_(nullptr),
@@ -1731,6 +1850,20 @@
lookup_table_ = TypeLookupTable::Open(dex_data, lookup_table_data_, num_class_defs);
}
}
+ DCHECK(!IsBackedByVdexOnly());
+}
+
+OatDexFile::OatDexFile(const OatFile* oat_file,
+ const DexFile* dex_file,
+ const std::string& dex_file_location,
+ const std::string& canonical_dex_file_location)
+ : oat_file_(oat_file),
+ dex_file_location_(dex_file_location),
+ canonical_dex_file_location_(canonical_dex_file_location),
+ dex_file_location_checksum_(dex_file->GetLocationChecksum()),
+ dex_file_pointer_(reinterpret_cast<const uint8_t*>(dex_file)) {
+ dex_file->SetOatDexFile(this);
+ DCHECK(IsBackedByVdexOnly());
}
OatDexFile::OatDexFile(TypeLookupTable&& lookup_table) : lookup_table_(std::move(lookup_table)) {
@@ -1765,7 +1898,24 @@
return oat_class_offsets_pointer_[class_def_index];
}
+bool OatDexFile::IsBackedByVdexOnly() const {
+ return oat_class_offsets_pointer_ == nullptr;
+}
+
OatFile::OatClass OatDexFile::GetOatClass(uint16_t class_def_index) const {
+ // If this is an OatFileBackedByVdex, initialize the OatClass using the vdex's VerifierDeps.
+ if (IsBackedByVdexOnly()) {
+ bool is_vdex_verified = down_cast<const OatFileBackedByVdex*>(oat_file_)->IsClassVerifiedInVdex(
+ *this,
+ class_def_index);
+ return OatFile::OatClass(oat_file_,
+ is_vdex_verified ? ClassStatus::kVerified : ClassStatus::kNotReady,
+ /* type= */ kOatClassNoneCompiled,
+ /* bitmap_size= */ 0u,
+ /* bitmap_pointer= */ nullptr,
+ /* methods_pointer= */ nullptr);
+ }
+
uint32_t oat_class_offset = GetOatClassOffset(class_def_index);
const uint8_t* oat_class_pointer = oat_file_->Begin() + oat_class_offset;
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 78ec969..fbe596e 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -124,6 +124,12 @@
const char* abs_dex_location,
std::string* error_msg);
+ // Initialize OatFile instance from an already loaded VdexFile. This assumes
+ // the vdex does not have a dex section and accepts a vector of DexFiles separately.
+ static OatFile* OpenFromVdex(const std::vector<const DexFile*>& dex_files,
+ std::unique_ptr<VdexFile>&& vdex_file,
+ const std::string& location);
+
virtual ~OatFile();
bool IsExecutable() const {
@@ -545,6 +551,15 @@
const uint32_t* oat_class_offsets_pointer,
const DexLayoutSections* dex_layout_sections);
+ // Create an OatDexFile wrapping an existing DexFile. Will set the OatDexFile
+ // pointer in the DexFile.
+ OatDexFile(const OatFile* oat_file,
+ const DexFile* dex_file,
+ const std::string& dex_file_location,
+ const std::string& canonical_dex_file_location);
+
+ bool IsBackedByVdexOnly() const;
+
static void AssertAotCompiler();
const OatFile* const oat_file_ = nullptr;
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index f9823a1..92acdd0 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -647,6 +647,16 @@
return dex_files;
}
+static std::vector<const DexFile::Header*> GetDexFileHeaders(const std::vector<MemMap>& maps) {
+ std::vector<const DexFile::Header*> headers;
+ headers.reserve(maps.size());
+ for (const MemMap& map : maps) {
+ DCHECK(map.IsValid());
+ headers.push_back(reinterpret_cast<const DexFile::Header*>(map.Begin()));
+ }
+ return headers;
+}
+
static std::vector<const DexFile::Header*> GetDexFileHeaders(
const std::vector<const DexFile*>& dex_files) {
std::vector<const DexFile::Header*> headers;
@@ -657,6 +667,134 @@
return headers;
}
+std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
+ std::vector<MemMap>&& dex_mem_maps,
+ jobject class_loader,
+ jobjectArray dex_elements,
+ const OatFile** out_oat_file,
+ std::vector<std::string>* error_msgs) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenDexFilesFromOat_Impl(
+ std::move(dex_mem_maps),
+ class_loader,
+ dex_elements,
+ out_oat_file,
+ error_msgs);
+
+ if (error_msgs->empty()) {
+ // Remove write permission from DexFile pages. We do this at the end because
+ // OatFile assigns OatDexFile pointer in the DexFile objects.
+ for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
+ if (!dex_file->DisableWrite()) {
+ error_msgs->push_back("Failed to make dex file " + dex_file->GetLocation() + " read-only");
+ }
+ }
+ }
+
+ if (!error_msgs->empty()) {
+ return std::vector<std::unique_ptr<const DexFile>>();
+ }
+
+ return dex_files;
+}
+
+std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat_Impl(
+ std::vector<MemMap>&& dex_mem_maps,
+ jobject class_loader,
+ jobjectArray dex_elements,
+ const OatFile** out_oat_file,
+ std::vector<std::string>* error_msgs) {
+ ScopedTrace trace(__FUNCTION__);
+ std::string error_msg;
+ DCHECK(error_msgs != nullptr);
+
+ // Extract dex file headers from `dex_mem_maps`.
+ const std::vector<const DexFile::Header*> dex_headers = GetDexFileHeaders(dex_mem_maps);
+
+ // Determine dex/vdex locations and the combined location checksum.
+ uint32_t location_checksum;
+ std::string dex_location;
+ std::string vdex_path;
+ bool has_vdex = OatFileAssistant::AnonymousDexVdexLocation(dex_headers,
+ kRuntimeISA,
+ &location_checksum,
+ &dex_location,
+ &vdex_path);
+
+ // Attempt to open an existing vdex and check dex file checksums match.
+ std::unique_ptr<VdexFile> vdex_file = nullptr;
+ if (has_vdex && OS::FileExists(vdex_path.c_str())) {
+ vdex_file = VdexFile::Open(vdex_path,
+ /* writable= */ false,
+ /* low_4gb= */ false,
+ /* unquicken= */ false,
+ &error_msg);
+ if (vdex_file == nullptr) {
+ LOG(WARNING) << "Failed to open vdex " << vdex_path << ": " << error_msg;
+ } else if (!vdex_file->MatchesDexFileChecksums(dex_headers)) {
+ LOG(WARNING) << "Failed to open vdex " << vdex_path << ": dex file checksum mismatch";
+ vdex_file.reset(nullptr);
+ }
+ }
+
+ // Load dex files. Skip structural dex file verification if vdex was found
+ // and dex checksums matched.
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ for (size_t i = 0; i < dex_mem_maps.size(); ++i) {
+ static constexpr bool kVerifyChecksum = true;
+ const ArtDexFileLoader dex_file_loader;
+ std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(
+ DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()),
+ location_checksum,
+ std::move(dex_mem_maps[i]),
+ /* verify= */ (vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),
+ kVerifyChecksum,
+ &error_msg));
+ if (dex_file != nullptr) {
+ dex::tracking::RegisterDexFile(dex_file.get()); // Register for tracking.
+ dex_files.push_back(std::move(dex_file));
+ } else {
+ error_msgs->push_back("Failed to open dex files from memory: " + error_msg);
+ }
+ }
+
+ // Check if we should proceed to creating an OatFile instance backed by the vdex.
+ // We need: (a) an existing vdex, (b) class loader (can be null if invoked via reflection),
+ // and (c) no errors during dex file loading.
+ if (vdex_file == nullptr || class_loader == nullptr || !error_msgs->empty()) {
+ return dex_files;
+ }
+
+ // Attempt to create a class loader context, check OpenDexFiles succeeds (prerequisite
+ // for using the context later).
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::CreateContextForClassLoader(
+ class_loader,
+ dex_elements);
+ if (context == nullptr) {
+ LOG(ERROR) << "Could not create class loader context for " << vdex_path;
+ return dex_files;
+ }
+ DCHECK(context->OpenDexFiles(kRuntimeISA, ""))
+ << "Context created from already opened dex files should not attempt to open again";
+
+ // Check that we can use the vdex against this boot class path and in this class loader context.
+ // Note 1: We do not need a class loader collision check because there is no compiled code.
+ // Note 2: If these checks fail, we cannot fast-verify because the vdex does not contain
+ // full VerifierDeps.
+ if (!vdex_file->MatchesBootClassPathChecksums() ||
+ !vdex_file->MatchesClassLoaderContext(*context.get())) {
+ return dex_files;
+ }
+
+ // Initialize an OatFile instance backed by the loaded vdex.
+ std::unique_ptr<OatFile> oat_file(OatFile::OpenFromVdex(MakeNonOwningPointerVector(dex_files),
+ std::move(vdex_file),
+ dex_location));
+ DCHECK(oat_file != nullptr);
+ VLOG(class_linker) << "Registering " << oat_file->GetLocation();
+ *out_oat_file = RegisterOatFile(std::move(oat_file));
+ return dex_files;
+}
+
// Check how many vdex files exist in the same directory as the vdex file we are about
// to write. If more than or equal to kAnonymousVdexCacheSize, unlink the least
// recently used one(s) (according to stat-reported atime).
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index e5eae9f..d09b6d6 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -37,6 +37,7 @@
class ClassLoaderContext;
class DexFile;
+class MemMap;
class OatFile;
class ThreadPool;
@@ -100,6 +101,23 @@
/*out*/ std::vector<std::string>* error_msgs)
REQUIRES(!Locks::oat_file_manager_lock_, !Locks::mutator_lock_);
+ // Opens dex files provided in `dex_mem_maps` and attempts to find an anonymous
+ // vdex file created during a previous load attempt. If found, will initialize
+ // an instance of OatFile to back the DexFiles and preverify them using the
+ // vdex's VerifierDeps.
+ //
+ // 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.
+ std::vector<std::unique_ptr<const DexFile>> OpenDexFilesFromOat(
+ std::vector<MemMap>&& dex_mem_maps,
+ jobject class_loader,
+ jobjectArray dex_elements,
+ /*out*/ const OatFile** out_oat_file,
+ /*out*/ std::vector<std::string>* error_msgs)
+ REQUIRES(!Locks::oat_file_manager_lock_, !Locks::mutator_lock_);
+
void DumpForSigQuit(std::ostream& os);
void SetOnlyUseSystemOatFiles(bool enforce, bool assert_no_files_loaded);
@@ -130,6 +148,14 @@
kPerformedHasCollisions,
};
+ std::vector<std::unique_ptr<const DexFile>> OpenDexFilesFromOat_Impl(
+ std::vector<MemMap>&& dex_mem_maps,
+ jobject class_loader,
+ jobjectArray dex_elements,
+ /*out*/ const OatFile** out_oat_file,
+ /*out*/ std::vector<std::string>* error_msgs)
+ REQUIRES(!Locks::oat_file_manager_lock_, !Locks::mutator_lock_);
+
// Check that the class loader context of the given oat file matches the given context.
// This will perform a check that all class loaders in the chain have the same type and
// classpath.
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 4b24769..cd60fab 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -423,4 +423,48 @@
return true;
}
+bool VdexFile::MatchesDexFileChecksums(const std::vector<const DexFile::Header*>& dex_headers)
+ const {
+ const VerifierDepsHeader& header = GetVerifierDepsHeader();
+ if (dex_headers.size() != header.GetNumberOfDexFiles()) {
+ LOG(WARNING) << "Mismatch of number of dex files in vdex (expected="
+ << header.GetNumberOfDexFiles() << ", actual=" << dex_headers.size() << ")";
+ return false;
+ }
+ const VdexChecksum* checksums = header.GetDexChecksumsArray();
+ for (size_t i = 0; i < dex_headers.size(); ++i) {
+ if (checksums[i] != dex_headers[i]->checksum_) {
+ LOG(WARNING) << "Mismatch of dex file checksum in vdex (index=" << i << ")";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VdexFile::MatchesBootClassPathChecksums() const {
+ ArrayRef<const uint8_t> data = GetBootClassPathChecksumData();
+ std::string vdex(reinterpret_cast<const char*>(data.data()), data.size());
+ std::string runtime = ComputeBootClassPathChecksumString();
+ if (vdex == runtime) {
+ return true;
+ } else {
+ LOG(WARNING) << "Mismatch of boot class path checksum in vdex (expected="
+ << vdex << ", actual=" << runtime << ")";
+ return false;
+ }
+}
+
+bool VdexFile::MatchesClassLoaderContext(const ClassLoaderContext& context) const {
+ ArrayRef<const uint8_t> data = GetClassLoaderContextData();
+ std::string spec(reinterpret_cast<const char*>(data.data()), data.size());
+ ClassLoaderContext::VerificationResult result = context.VerifyClassLoaderContextMatch(spec);
+ if (result != ClassLoaderContext::VerificationResult::kMismatch) {
+ return true;
+ } else {
+ LOG(WARNING) << "Mismatch of class loader context in vdex (expected="
+ << spec << ", actual=" << context.EncodeContextForOatFile("") << ")";
+ return false;
+ }
+}
+
} // namespace art
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 88114fb..102b73f 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -342,6 +342,18 @@
const std::string& class_loader_context,
std::string* error_msg);
+ // Returns true if the dex file checksums stored in the vdex header match
+ // the checksums in `dex_headers`. Both the number of dex files and their
+ // order must match too.
+ bool MatchesDexFileChecksums(const std::vector<const DexFile::Header*>& dex_headers) const;
+
+ // Returns true if the boot class path checksum stored in the vdex matches
+ // the checksum of boot class path in the current runtime.
+ bool MatchesBootClassPathChecksums() const;
+
+ // Returns true if the class loader context stored in the vdex matches `context`.
+ bool MatchesClassLoaderContext(const ClassLoaderContext& context) const;
+
private:
uint32_t GetQuickeningInfoTableOffset(const uint8_t* source_dex_begin) const;
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index b45f143..ed5488c 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -737,6 +737,25 @@
}
}
+void VerifierDeps::DecodeDexFileDeps(DexFileDeps& deps,
+ const uint8_t** data_start,
+ const uint8_t* data_end) {
+ DecodeStringVector(data_start, data_end, &deps.strings_);
+ DecodeSet(data_start, data_end, &deps.assignable_types_);
+ DecodeSet(data_start, data_end, &deps.unassignable_types_);
+ DecodeSet(data_start, data_end, &deps.classes_);
+ DecodeSet(data_start, data_end, &deps.fields_);
+ DecodeSet(data_start, data_end, &deps.methods_);
+ DecodeUint16SparseBitVector(data_start,
+ data_end,
+ &deps.verified_classes_,
+ /* sparse_value= */ false);
+ DecodeUint16SparseBitVector(data_start,
+ data_end,
+ &deps.redefined_classes_,
+ /* sparse_value= */ true);
+}
+
VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files,
ArrayRef<const uint8_t> data)
: VerifierDeps(dex_files, /*output_only=*/ false) {
@@ -750,24 +769,30 @@
const uint8_t* data_end = data_start + data.size();
for (const DexFile* dex_file : dex_files) {
DexFileDeps* deps = GetDexFileDeps(*dex_file);
- DecodeStringVector(&data_start, data_end, &deps->strings_);
- DecodeSet(&data_start, data_end, &deps->assignable_types_);
- DecodeSet(&data_start, data_end, &deps->unassignable_types_);
- DecodeSet(&data_start, data_end, &deps->classes_);
- DecodeSet(&data_start, data_end, &deps->fields_);
- DecodeSet(&data_start, data_end, &deps->methods_);
- DecodeUint16SparseBitVector(&data_start,
- data_end,
- &deps->verified_classes_,
- /* sparse_value= */ false);
- DecodeUint16SparseBitVector(&data_start,
- data_end,
- &deps->redefined_classes_,
- /* sparse_value= */ true);
+ DecodeDexFileDeps(*deps, &data_start, data_end);
}
CHECK_LE(data_start, data_end);
}
+std::vector<std::vector<bool>> VerifierDeps::ParseVerifiedClasses(
+ const std::vector<const DexFile*>& dex_files,
+ ArrayRef<const uint8_t> data) {
+ DCHECK(!data.empty());
+ DCHECK(!dex_files.empty());
+
+ std::vector<std::vector<bool>> verified_classes_per_dex;
+ verified_classes_per_dex.reserve(dex_files.size());
+
+ const uint8_t* data_start = data.data();
+ const uint8_t* data_end = data_start + data.size();
+ for (const DexFile* dex_file : dex_files) {
+ DexFileDeps deps(dex_file->NumClassDefs());
+ DecodeDexFileDeps(deps, &data_start, data_end);
+ verified_classes_per_dex.push_back(std::move(deps.verified_classes_));
+ }
+ return verified_classes_per_dex;
+}
+
bool VerifierDeps::Equals(const VerifierDeps& rhs) const {
if (dex_deps_.size() != rhs.dex_deps_.size()) {
return false;
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index 5002db0..3980ca2 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -144,6 +144,13 @@
return output_only_;
}
+ // Parses raw VerifierDeps data to extract bitvectors of which class def indices
+ // were verified or not. The given `dex_files` must match the order and count of
+ // dex files used to create the VerifierDeps.
+ static std::vector<std::vector<bool>> ParseVerifiedClasses(
+ const std::vector<const DexFile*>& dex_files,
+ ArrayRef<const uint8_t> data);
+
private:
static constexpr uint16_t kUnresolvedMarker = static_cast<uint16_t>(-1);
@@ -234,6 +241,11 @@
VerifierDeps(const std::vector<const DexFile*>& dex_files, bool output_only);
+ // Helper function to share DexFileDeps decoding code.
+ static void DecodeDexFileDeps(DexFileDeps& deps,
+ const uint8_t** data_start,
+ const uint8_t* data_end);
+
// Finds the DexFileDep instance associated with `dex_file`, or nullptr if
// `dex_file` is not reported as being compiled.
DexFileDeps* GetDexFileDeps(const DexFile& dex_file);
diff --git a/test/692-vdex-inmem-loader/expected.txt b/test/692-vdex-inmem-loader/expected.txt
index a127604..889d7d1 100644
--- a/test/692-vdex-inmem-loader/expected.txt
+++ b/test/692-vdex-inmem-loader/expected.txt
@@ -2,3 +2,7 @@
Hello
Hello
Hello
+Hello
+Hello
+Hello
+Hello
diff --git a/test/692-vdex-inmem-loader/src/Main.java b/test/692-vdex-inmem-loader/src/Main.java
index 75aef8b..4d21f5b 100644
--- a/test/692-vdex-inmem-loader/src/Main.java
+++ b/test/692-vdex-inmem-loader/src/Main.java
@@ -42,13 +42,18 @@
private static void test(ClassLoader loader,
boolean expectedHasVdexFile,
+ boolean expectedBackedByOat,
boolean invokeMethod) throws Exception {
// If ART created a vdex file, it must have verified all the classes.
- boolean expectedClassesVerified = expectedHasVdexFile;
+ // That happens if and only if we expect a vdex at the end of the test but
+ // do not expect it to have been loaded.
+ boolean expectedClassesVerified = expectedHasVdexFile && !expectedBackedByOat;
waitForVerifier();
check(expectedClassesVerified, areClassesVerified(loader), "areClassesVerified");
check(expectedHasVdexFile, hasVdexFile(loader), "areClassesVerified");
+ check(expectedBackedByOat, isBackedByOatFile(loader), "isBackedByOatFile");
+ check(expectedBackedByOat, areClassesPreverified(loader), "areClassesPreverified");
if (invokeMethod) {
loader.loadClass("art.ClassB").getDeclaredMethod("printHello").invoke(null);
@@ -58,24 +63,47 @@
public static void main(String[] args) throws Exception {
System.loadLibrary(args[0]);
ClassLoader[] loaders = null;
+
+ // Feature is disabled in debuggable mode because runtime threads are not
+ // allowed to load classes.
boolean featureEnabled = !isDebuggable();
// Data directory not set. Background verification job should not have run
// and vdex should not have been created.
- test(singleLoader(), /*hasVdex*/ false, /*invokeMethod*/ true);
+ test(singleLoader(), /*hasVdex*/ false, /*backedByOat*/ false, /*invokeMethod*/ true);
// Set data directory for this process.
setProcessDataDir(DEX_LOCATION);
// Data directory is now set. Background verification job should have run,
// should have verified classes and written results to a vdex.
- test(singleLoader(), /*hasVdex*/ featureEnabled, /*invokeMethod*/ true);
+ test(singleLoader(), /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ true);
+ test(singleLoader(), /*hasVdex*/ featureEnabled, /*backedByOat*/ true, /*invokeMethod*/ true);
// Test loading the two dex files with separate class loaders.
// Background verification task should still verify all classes.
loaders = multiLoader();
- test(loaders[0], /*hasVdex*/ featureEnabled, /*invokeMethod*/ false);
- test(loaders[1], /*hasVdex*/ featureEnabled, /*invokeMethod*/ true);
+ test(loaders[0], /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ false);
+ test(loaders[1], /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ true);
+
+ loaders = multiLoader();
+ test(loaders[0], /*hasVdex*/ featureEnabled, /*backedByOat*/ featureEnabled,
+ /*invokeMethod*/ false);
+ test(loaders[1], /*hasVdex*/ featureEnabled, /*backedByOat*/ featureEnabled,
+ /*invokeMethod*/ true);
+
+ // Change boot classpath checksum.
+ appendToBootClassLoader(DEX_EXTRA, /*isCorePlatform*/ false);
+
+ loaders = multiLoader();
+ test(loaders[0], /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ false);
+ test(loaders[1], /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ true);
+
+ loaders = multiLoader();
+ test(loaders[0], /*hasVdex*/ featureEnabled, /*backedByOat*/ featureEnabled,
+ /*invokeMethod*/ false);
+ test(loaders[1], /*hasVdex*/ featureEnabled, /*backedByOat*/ featureEnabled,
+ /*invokeMethod*/ true);
}
private static native boolean isDebuggable();
@@ -83,6 +111,8 @@
private static native void waitForVerifier();
private static native boolean areClassesVerified(ClassLoader loader);
private static native boolean hasVdexFile(ClassLoader loader);
+ private static native boolean isBackedByOatFile(ClassLoader loader);
+ private static native boolean areClassesPreverified(ClassLoader loader);
// Defined in 674-hiddenapi.
private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
diff --git a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
index a5d09e9..a10e2e7 100644
--- a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
+++ b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
@@ -102,6 +102,77 @@
OS::FileExists(vdex_filename.c_str());
}
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isBackedByOatFile(JNIEnv*,
+ jclass,
+ jobject loader) {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::ClassLoader> h_loader = hs.NewHandle(soa.Decode<mirror::ClassLoader>(loader));
+
+ bool is_first = true;
+ bool all_backed_by_oat = false;
+
+ VisitClassLoaderDexFiles(
+ soa,
+ h_loader,
+ [&](const DexFile* dex_file) {
+ bool is_backed_by_oat = (dex_file->GetOatDexFile() != nullptr);
+ if (is_first) {
+ all_backed_by_oat = is_backed_by_oat;
+ is_first = false;
+ } else if (all_backed_by_oat != is_backed_by_oat) {
+ // DexFiles should either all or none be backed by oat.
+ LOG(ERROR) << "isBackedByOatFile is inconsistent";
+ }
+ return true;
+ });
+ return all_backed_by_oat ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_areClassesPreverified(JNIEnv*,
+ jclass,
+ jobject loader) {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> h_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(loader)));
+
+ std::vector<const DexFile*> dex_files;
+ VisitClassLoaderDexFiles(
+ soa,
+ h_loader,
+ [&](const DexFile* dex_file) {
+ dex_files.push_back(dex_file);
+ return true;
+ });
+
+ MutableHandle<mirror::Class> h_class(hs.NewHandle<mirror::Class>(nullptr));
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
+ bool is_first = true;
+ bool all_preverified = false;
+ for (const DexFile* dex_file : dex_files) {
+ for (uint16_t cdef_idx = 0; cdef_idx < dex_file->NumClassDefs(); ++cdef_idx) {
+ const char* desc = dex_file->GetClassDescriptor(dex_file->GetClassDef(cdef_idx));
+ h_class.Assign(class_linker->FindClass(soa.Self(), desc, h_loader));
+ CHECK(h_class != nullptr) << "Could not find class " << desc;
+
+ ClassStatus oat_file_class_status(ClassStatus::kNotReady);
+ bool is_preverified = class_linker->VerifyClassUsingOatFile(
+ *dex_file, h_class.Get(), oat_file_class_status);
+
+ if (is_first) {
+ all_preverified = is_preverified;
+ is_first = false;
+ } else if (all_preverified != is_preverified) {
+ // Classes should either all or none be preverified.
+ LOG(ERROR) << "areClassesPreverified is inconsistent";
+ }
+ }
+ }
+
+ return all_preverified ? JNI_TRUE : JNI_FALSE;
+}
+
extern "C" JNIEXPORT jint JNICALL Java_Main_getVdexCacheSize(JNIEnv*, jclass) {
return static_cast<jint>(OatFileManager::kAnonymousVdexCacheSize);
}