/*
|
* 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"
|