| /* |
| * 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 <inttypes.h> |
| #include <stdint.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <cstring> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/macros.h> |
| #include <android-base/mapped_file.h> |
| #include <android-base/stringprintf.h> |
| |
| #include <dex/class_accessor-inl.h> |
| #include <dex/code_item_accessors-inl.h> |
| #include <dex/dex_file-inl.h> |
| #include <dex/dex_file_loader.h> |
| |
| namespace art { |
| namespace { |
| |
| struct MethodCacheEntry { |
| int32_t offset; // Offset relative to the start of the dex file header. |
| int32_t len; |
| int32_t index; // Method index. |
| }; |
| |
| class MappedFileContainer : public DexFileContainer { |
| public: |
| explicit MappedFileContainer(std::unique_ptr<android::base::MappedFile>&& map) |
| : map_(std::move(map)) {} |
| ~MappedFileContainer() override {} |
| int GetPermissions() override { return 0; } |
| bool IsReadOnly() override { return true; } |
| bool EnableWrite() override { return false; } |
| bool DisableWrite() override { return false; } |
| |
| private: |
| std::unique_ptr<android::base::MappedFile> map_; |
| DISALLOW_COPY_AND_ASSIGN(MappedFileContainer); |
| }; |
| |
| } // namespace |
| } // namespace art |
| |
| extern "C" { |
| |
| struct ExtDexFileString { |
| const std::string str_; |
| }; |
| |
| static const ExtDexFileString empty_string{""}; |
| |
| const ExtDexFileString* ExtDexFileMakeString(const char* str, size_t size) { |
| if (size == 0) { |
| return &empty_string; |
| } |
| return new ExtDexFileString{std::string(str, size)}; |
| } |
| |
| const char* ExtDexFileGetString(const ExtDexFileString* ext_string, /*out*/ size_t* size) { |
| DCHECK(ext_string != nullptr); |
| *size = ext_string->str_.size(); |
| return ext_string->str_.data(); |
| } |
| |
| void ExtDexFileFreeString(const ExtDexFileString* ext_string) { |
| DCHECK(ext_string != nullptr); |
| if (ext_string != &empty_string) { |
| delete (ext_string); |
| } |
| } |
| |
| // Wraps DexFile to add the caching needed by the external interface. This is |
| // what gets passed over as ExtDexFile*. |
| struct ExtDexFile { |
| private: |
| // Method cache for GetMethodInfoForOffset. This is populated as we iterate |
| // sequentially through the class defs. MethodCacheEntry.name is only set for |
| // methods returned by GetMethodInfoForOffset. |
| std::map<int32_t, art::MethodCacheEntry> method_cache_; |
| |
| // Index of first class def for which method_cache_ isn't complete. |
| uint32_t class_def_index_ = 0; |
| |
| public: |
| std::unique_ptr<const art::DexFile> dex_file_; |
| explicit ExtDexFile(std::unique_ptr<const art::DexFile>&& dex_file) |
| : dex_file_(std::move(dex_file)) {} |
| |
| art::MethodCacheEntry* GetMethodCacheEntryForOffset(int64_t dex_offset) { |
| // First look in the method cache. |
| auto it = method_cache_.upper_bound(dex_offset); |
| if (it != method_cache_.end() && dex_offset >= it->second.offset) { |
| return &it->second; |
| } |
| |
| for (; class_def_index_ < dex_file_->NumClassDefs(); 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; |
| } |
| |
| int32_t offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file_->Begin(); |
| int32_t len = code.InsnsSizeInBytes(); |
| int32_t index = method.GetIndex(); |
| auto res = method_cache_.emplace(offset + len, art::MethodCacheEntry{offset, len, index}); |
| if (offset <= dex_offset && dex_offset < offset + len) { |
| return &res.first->second; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| }; |
| |
| int ExtDexFileOpenFromMemory(const void* addr, |
| /*inout*/ size_t* size, |
| const char* location, |
| /*out*/ const ExtDexFileString** ext_error_msg, |
| /*out*/ ExtDexFile** ext_dex_file) { |
| if (*size < sizeof(art::DexFile::Header)) { |
| *size = sizeof(art::DexFile::Header); |
| *ext_error_msg = nullptr; |
| return false; |
| } |
| |
| const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(addr); |
| 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)) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("Corrupt CompactDexFile header in '%s'", location)}; |
| return false; |
| } |
| if (computed_file_size > file_size) { |
| file_size = computed_file_size; |
| } |
| } else if (!art::StandardDexFile::IsMagicValid(header->magic_)) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("Unrecognized dex file header in '%s'", location)}; |
| return false; |
| } |
| |
| if (*size < file_size) { |
| *size = file_size; |
| *ext_error_msg = nullptr; |
| return false; |
| } |
| |
| std::string loc_str(location); |
| art::DexFileLoader loader; |
| std::string error_msg; |
| std::unique_ptr<const art::DexFile> dex_file = loader.Open(static_cast<const uint8_t*>(addr), |
| *size, |
| loc_str, |
| header->checksum_, |
| /*oat_dex_file=*/nullptr, |
| /*verify=*/false, |
| /*verify_checksum=*/false, |
| &error_msg); |
| if (dex_file == nullptr) { |
| *ext_error_msg = new ExtDexFileString{std::move(error_msg)}; |
| return false; |
| } |
| |
| *ext_dex_file = new ExtDexFile(std::move(dex_file)); |
| return true; |
| } |
| |
| int ExtDexFileOpenFromFd(int fd, |
| off_t offset, |
| const char* location, |
| /*out*/ const ExtDexFileString** ext_error_msg, |
| /*out*/ ExtDexFile** ext_dex_file) { |
| size_t length; |
| { |
| struct stat sbuf; |
| std::memset(&sbuf, 0, sizeof(sbuf)); |
| if (fstat(fd, &sbuf) == -1) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("fstat '%s' failed: %s", location, std::strerror(errno))}; |
| return false; |
| } |
| if (S_ISDIR(sbuf.st_mode)) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("Attempt to mmap directory '%s'", location)}; |
| return false; |
| } |
| length = sbuf.st_size; |
| } |
| |
| if (length < offset + sizeof(art::DexFile::Header)) { |
| *ext_error_msg = new ExtDexFileString{android::base::StringPrintf( |
| "Offset %" PRId64 " too large for '%s' of size %zu", |
| int64_t{offset}, |
| location, |
| length)}; |
| return false; |
| } |
| |
| // Cannot use MemMap in libartbase here, because it pulls in dlopen which we |
| // can't have when being compiled statically. |
| std::unique_ptr<android::base::MappedFile> map = |
| android::base::MappedFile::FromFd(fd, offset, length, PROT_READ); |
| if (map == nullptr) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("mmap '%s' failed: %s", location, std::strerror(errno))}; |
| return false; |
| } |
| |
| const art::DexFile::Header* header = reinterpret_cast<const art::DexFile::Header*>(map->data()); |
| uint32_t file_size; |
| if (__builtin_add_overflow(offset, header->file_size_, &file_size)) { |
| *ext_error_msg = |
| new ExtDexFileString{android::base::StringPrintf("Corrupt header in '%s'", location)}; |
| return false; |
| } |
| if (length < file_size) { |
| *ext_error_msg = new ExtDexFileString{ |
| android::base::StringPrintf("Dex file '%s' too short: expected %" PRIu32 ", got %" PRIu64, |
| location, |
| file_size, |
| uint64_t{length})}; |
| return false; |
| } |
| |
| void* addr = map->data(); |
| size_t size = map->size(); |
| auto container = std::make_unique<art::MappedFileContainer>(std::move(map)); |
| |
| std::string loc_str(location); |
| std::string error_msg; |
| art::DexFileLoader loader; |
| std::unique_ptr<const art::DexFile> dex_file = loader.Open(reinterpret_cast<const uint8_t*>(addr), |
| size, |
| loc_str, |
| header->checksum_, |
| /*oat_dex_file=*/nullptr, |
| /*verify=*/false, |
| /*verify_checksum=*/false, |
| &error_msg, |
| std::move(container)); |
| if (dex_file == nullptr) { |
| *ext_error_msg = new ExtDexFileString{std::move(error_msg)}; |
| return false; |
| } |
| *ext_dex_file = new ExtDexFile(std::move(dex_file)); |
| return true; |
| } |
| |
| int ExtDexFileGetMethodInfoForOffset(ExtDexFile* ext_dex_file, |
| int64_t dex_offset, |
| int with_signature, |
| /*out*/ ExtDexFileMethodInfo* method_info) { |
| if (!ext_dex_file->dex_file_->IsInDataSection(ext_dex_file->dex_file_->Begin() + dex_offset)) { |
| return false; // The DEX offset is not within the bytecode of this dex file. |
| } |
| |
| if (ext_dex_file->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 = |
| ext_dex_file->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 false; // The DEX offset is not within the bytecode of this dex file. |
| } |
| } |
| |
| art::MethodCacheEntry* entry = ext_dex_file->GetMethodCacheEntryForOffset(dex_offset); |
| if (entry != nullptr) { |
| method_info->offset = entry->offset; |
| method_info->len = entry->len; |
| method_info->name = |
| new ExtDexFileString{ext_dex_file->dex_file_->PrettyMethod(entry->index, with_signature)}; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void ExtDexFileGetAllMethodInfos(ExtDexFile* ext_dex_file, |
| int with_signature, |
| ExtDexFileMethodInfoCallback* method_info_cb, |
| void* user_data) { |
| for (art::ClassAccessor accessor : ext_dex_file->dex_file_->GetClasses()) { |
| for (const art::ClassAccessor::Method& method : accessor.GetMethods()) { |
| art::CodeItemInstructionAccessor code = method.GetInstructions(); |
| if (!code.HasCodeItem()) { |
| continue; |
| } |
| |
| ExtDexFileMethodInfo method_info; |
| method_info.offset = static_cast<int32_t>(reinterpret_cast<const uint8_t*>(code.Insns()) - |
| ext_dex_file->dex_file_->Begin()); |
| method_info.len = code.InsnsSizeInBytes(); |
| method_info.name = new ExtDexFileString{ |
| ext_dex_file->dex_file_->PrettyMethod(method.GetIndex(), with_signature)}; |
| method_info_cb(&method_info, user_data); |
| } |
| } |
| } |
| |
| void ExtDexFileFree(ExtDexFile* ext_dex_file) { delete (ext_dex_file); } |
| |
| } // extern "C" |