/* * Copyright (C) 2018 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 "art_api/dex_file_external.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { struct ADexFile_Method { ADexFile* adex; uint32_t index; size_t offset; size_t size; }; // Opaque implementation of ADexFile for the C interface. struct ADexFile { explicit ADexFile(std::unique_ptr dex_file) : dex_file_(std::move(dex_file)) {} inline bool FindMethod(uint32_t dex_offset, /*out*/ ADexFile_Method* result) { uint32_t class_def_index; if (GetClassDefIndex(dex_offset, &class_def_index)) { art::ClassAccessor accessor(*dex_file_, class_def_index); for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { art::CodeItemInstructionAccessor code = method.GetInstructions(); if (!code.HasCodeItem()) { continue; } size_t offset = reinterpret_cast(code.Insns()) - dex_file_->Begin(); size_t size = code.InsnsSizeInBytes(); if (offset <= dex_offset && dex_offset < offset + size) { *result = ADexFile_Method { .adex = this, .index = method.GetIndex(), .offset = offset, .size = size, }; return true; } } } return false; } void CreateClassCache() { // Create binary search table with (end_dex_offset, class_def_index) entries. // That is, we don't assume that dex code of given class is consecutive. std::deque> cache; for (art::ClassAccessor accessor : dex_file_->GetClasses()) { for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { art::CodeItemInstructionAccessor code = method.GetInstructions(); if (code.HasCodeItem()) { int32_t offset = reinterpret_cast(code.Insns()) - dex_file_->Begin(); DCHECK_NE(offset, 0); cache.emplace_back(offset + code.InsnsSizeInBytes(), accessor.GetClassDefIndex()); } } } std::sort(cache.begin(), cache.end()); // If two consecutive methods belong to same class, we can merge them. // This tends to reduce the number of entries (used memory) by 10x. size_t num_entries = cache.size(); if (cache.size() > 1) { for (auto it = std::next(cache.begin()); it != cache.end(); it++) { if (std::prev(it)->second == it->second) { std::prev(it)->first = 0; // Clear entry with lower end_dex_offset (mark to remove). num_entries--; } } } // The cache is immutable now. Store it as continuous vector to save space. class_cache_.reserve(num_entries); auto pred = [](auto it) { return it.first != 0; }; // Entries to copy (not cleared above). std::copy_if(cache.begin(), cache.end(), std::back_inserter(class_cache_), pred); } inline bool GetClassDefIndex(uint32_t dex_offset, uint32_t* class_def_index) { if (class_cache_.empty()) { CreateClassCache(); } // Binary search in the class cache. First element of the pair is the key. auto comp = [](uint32_t value, const auto& it) { return value < it.first; }; auto it = std::upper_bound(class_cache_.begin(), class_cache_.end(), dex_offset, comp); if (it != class_cache_.end()) { *class_def_index = it->second; return true; } return false; } // The underlying ART object. std::unique_ptr dex_file_; // Binary search table with (end_dex_offset, class_def_index) entries. std::vector> class_cache_; // Used as short lived temporary when needed. Avoids alloc/free. std::string temporary_qualified_name_; }; ADexFile_Error ADexFile_create(const void* _Nonnull address, size_t size, size_t* _Nullable new_size, const char* _Nonnull location, /*out*/ ADexFile* _Nullable * _Nonnull out_dex_file) { *out_dex_file = nullptr; if (size < sizeof(art::DexFile::Header)) { if (new_size != nullptr) { *new_size = sizeof(art::DexFile::Header); } return ADEXFILE_ERROR_NOT_ENOUGH_DATA; } const art::DexFile::Header* header = reinterpret_cast(address); uint32_t file_size = header->file_size_; if (art::CompactDexFile::IsMagicValid(header->magic_)) { // Compact dex files store the data section separately so that it can be shared. // Therefore we need to extend the read memory range to include it. // TODO: This might be wasteful as we might read data in between as well. // In practice, this should be fine, as such sharing only happens on disk. uint32_t computed_file_size; if (__builtin_add_overflow(header->data_off_, header->data_size_, &computed_file_size)) { return ADEXFILE_ERROR_INVALID_HEADER; } if (computed_file_size > file_size) { file_size = computed_file_size; } } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) { return ADEXFILE_ERROR_INVALID_HEADER; } if (size < file_size) { if (new_size != nullptr) { *new_size = file_size; } return ADEXFILE_ERROR_NOT_ENOUGH_DATA; } std::string loc_str(location); art::DexFileLoader loader; std::string error_msg; std::unique_ptr dex_file = loader.Open(static_cast(address), size, loc_str, header->checksum_, /*oat_dex_file=*/nullptr, /*verify=*/false, /*verify_checksum=*/false, &error_msg); if (dex_file == nullptr) { LOG(ERROR) << "Can not open dex file " << loc_str << ": " << error_msg; return ADEXFILE_ERROR_INVALID_DEX; } *out_dex_file = new ADexFile(std::move(dex_file)); return ADEXFILE_ERROR_OK; } void ADexFile_destroy(ADexFile* self) { delete self; } size_t ADexFile_findMethodAtOffset(ADexFile* self, size_t dex_offset, ADexFile_MethodCallback* callback, void* callback_data) { const art::DexFile* dex_file = self->dex_file_.get(); if (!dex_file->IsInDataSection(dex_file->Begin() + dex_offset)) { return 0; // The DEX offset is not within the bytecode of this dex file. } if (dex_file->IsCompactDexFile()) { // The data section of compact dex files might be shared. // Check the subrange unique to this compact dex. const art::CompactDexFile::Header& cdex_header = dex_file->AsCompactDexFile()->GetHeader(); uint32_t begin = cdex_header.data_off_ + cdex_header.OwnedDataBegin(); uint32_t end = cdex_header.data_off_ + cdex_header.OwnedDataEnd(); if (dex_offset < begin || dex_offset >= end) { return 0; // The DEX offset is not within the bytecode of this dex file. } } ADexFile_Method info; if (!self->FindMethod(dex_offset, &info)) { return 0; } callback(callback_data, &info); return 1; } size_t ADexFile_forEachMethod(ADexFile* self, ADexFile_MethodCallback* callback, void* callback_data) { size_t count = 0; for (art::ClassAccessor accessor : self->dex_file_->GetClasses()) { for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { art::CodeItemInstructionAccessor code = method.GetInstructions(); if (code.HasCodeItem()) { size_t offset = reinterpret_cast(code.Insns()) - self->dex_file_->Begin(); ADexFile_Method info { .adex = self, .index = method.GetIndex(), .offset = offset, .size = code.InsnsSizeInBytes(), }; callback(callback_data, &info); count++; } } } return count; } size_t ADexFile_Method_getCodeOffset(const ADexFile_Method* self, size_t* out_size) { if (out_size != nullptr) { *out_size = self->size; } return self->offset; } const char* ADexFile_Method_getName(const ADexFile_Method* self, size_t* out_size) { const char* name = self->adex->dex_file_->GetMethodName(self->index); if (out_size != nullptr) { *out_size = strlen(name); } return name; } const char* ADexFile_Method_getQualifiedName(const ADexFile_Method* self, int with_params, size_t* out_size) { std::string& temp = self->adex->temporary_qualified_name_; temp.clear(); self->adex->dex_file_->AppendPrettyMethod(self->index, with_params, &temp); if (out_size != nullptr) { *out_size = temp.size(); } return temp.data(); } const char* ADexFile_Method_getClassDescriptor(const ADexFile_Method* self, size_t* out_size) { const art::dex::MethodId& method_id = self->adex->dex_file_->GetMethodId(self->index); const char* name = self->adex->dex_file_->GetMethodDeclaringClassDescriptor(method_id); if (out_size != nullptr) { *out_size = strlen(name); } return name; } const char* ADexFile_Error_toString(ADexFile_Error self) { switch (self) { case ADEXFILE_ERROR_OK: return "Ok"; case ADEXFILE_ERROR_INVALID_DEX: return "Dex file is invalid."; case ADEXFILE_ERROR_NOT_ENOUGH_DATA: return "Not enough data. Incomplete dex file."; case ADEXFILE_ERROR_INVALID_HEADER: return "Invalid dex file header."; } return nullptr; } } // extern "C"