blob: 23f4ccd96af4e7dee7c8e14e65dda2703d760f14 [file] [log] [blame]
/*
* 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 "JITDebugReader.h"
#include <inttypes.h>
#include <sys/uio.h>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include "dso.h"
#include "environment.h"
#include "read_apk.h"
#include "read_elf.h"
#include "utils.h"
namespace simpleperf {
// To avoid too long time reading the jit/dex linked list, set an uplimit of entries read from the
// linked list.
static constexpr size_t MAX_LINKLED_LIST_LENGTH = 1024u;
// If the size of a symfile is larger than EXPECTED_MAX_SYMFILE_SIZE, we don't want to read it
// remotely.
static constexpr size_t MAX_JIT_SYMFILE_SIZE = 1024 * 1024u;
// Match the format of JITDescriptor in art/runtime/jit/debugger_itnerface.cc.
template <typename ADDRT>
struct JITDescriptor {
uint32_t version;
uint32_t action_flag;
ADDRT relevant_entry_addr;
ADDRT first_entry_addr;
uint8_t magic[8];
uint32_t flags;
uint32_t sizeof_descriptor;
uint32_t sizeof_entry;
uint32_t action_seqlock; // incremented before and after any modification
uint64_t action_timestamp; // CLOCK_MONOTONIC time of last action
bool Valid() const {
return version == 1 && strncmp(reinterpret_cast<const char*>(magic), "Android1", 8) == 0;
}
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_itnerface.cc.
template <typename ADDRT>
struct JITCodeEntry {
ADDRT next_addr;
ADDRT prev_addr;
ADDRT symfile_addr;
uint64_t symfile_size;
uint64_t register_timestamp; // CLOCK_MONOTONIC time of entry registration
bool Valid() const {
return symfile_addr > 0u && symfile_size > 0u;
}
};
// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc.
template <typename ADDRT>
struct __attribute__((packed)) PackedJITCodeEntry {
ADDRT next_addr;
ADDRT prev_addr;
ADDRT symfile_addr;
uint64_t symfile_size;
uint64_t register_timestamp;
bool Valid() const {
return symfile_addr > 0u && symfile_size > 0u;
}
};
using JITDescriptor32 = JITDescriptor<uint32_t>;
using JITDescriptor64 = JITDescriptor<uint64_t>;
#if defined(__x86_64__)
// Make sure simpleperf built for i386 and x86_64 see the correct JITCodeEntry layout of i386.
using JITCodeEntry32 = PackedJITCodeEntry<uint32_t>;
#else
using JITCodeEntry32 = JITCodeEntry<uint32_t>;
#endif
using JITCodeEntry64 = JITCodeEntry<uint64_t>;
// We want to support both 64-bit and 32-bit simpleperf when profiling either 64-bit or 32-bit
// apps. So using static_asserts to make sure that simpleperf on arm and aarch64 having the same
// view of structures, and simpleperf on i386 and x86_64 having the same view of structures.
static_assert(sizeof(JITDescriptor32) == 48, "");
static_assert(sizeof(JITDescriptor64) == 56, "");
#if defined(__i386__) or defined(__x86_64__)
static_assert(sizeof(JITCodeEntry32) == 28, "");
#else
static_assert(sizeof(JITCodeEntry32) == 32, "");
#endif
static_assert(sizeof(JITCodeEntry64) == 40, "");
JITDebugReader::JITDebugReader(pid_t pid, bool keep_symfiles)
: pid_(pid),
keep_symfiles_(keep_symfiles),
initialized_(false) {
TryInit();
}
void JITDebugReader::ReadUpdate(std::vector<JITSymFile>* new_jit_symfiles,
std::vector<DexSymFile>* new_dex_symfiles) {
if (!TryInit()) {
return;
}
// 1. Read descriptors.
Descriptor jit_descriptor;
Descriptor dex_descriptor;
if (!ReadDescriptors(&jit_descriptor, &dex_descriptor)) {
return;
}
// 2. Return if descriptors are not changed.
if (jit_descriptor.action_seqlock == last_jit_descriptor_.action_seqlock &&
dex_descriptor.action_seqlock == last_dex_descriptor_.action_seqlock) {
return;
}
// 3. Read new symfiles.
auto check_descriptor = [&](Descriptor& descriptor, bool is_jit) {
Descriptor tmp_jit_descriptor;
Descriptor tmp_dex_descriptor;
if (!ReadDescriptors(&tmp_jit_descriptor, &tmp_dex_descriptor)) {
return false;
}
if (is_jit) {
return descriptor.action_seqlock == tmp_jit_descriptor.action_seqlock;
}
return descriptor.action_seqlock == tmp_dex_descriptor.action_seqlock;
};
auto read_new_symfiles = [&](Descriptor& new_descriptor, Descriptor& old_descriptor,
bool is_jit) {
bool has_update = new_descriptor.action_seqlock != old_descriptor.action_seqlock &&
(new_descriptor.action_seqlock & 1) == 0;
if (!has_update) {
return false;
}
std::vector<CodeEntry> new_entries;
if (!ReadNewCodeEntries(new_descriptor, old_descriptor.action_timestamp, &new_entries)) {
return false;
}
// Check if the descriptor was changed while we were reading new entries.
if (!check_descriptor(new_descriptor, is_jit)) {
return false;
}
if (new_entries.empty()) {
return true;
}
if (is_jit) {
ReadJITSymFiles(new_entries, new_jit_symfiles);
} else {
ReadDexSymFiles(new_entries, new_dex_symfiles);
}
// Check if the descriptor was changed while we were reading symfiles.
if (!check_descriptor(new_descriptor, is_jit)) {
if (is_jit) {
new_jit_symfiles->clear();
} else {
new_dex_symfiles->clear();
}
return false;
}
return true;
};
if (read_new_symfiles(jit_descriptor, last_jit_descriptor_, true)) {
last_jit_descriptor_ = jit_descriptor;
}
if (read_new_symfiles(dex_descriptor, last_dex_descriptor_, false)) {
last_dex_descriptor_ = dex_descriptor;
}
}
bool JITDebugReader::TryInit() {
if (initialized_) {
return true;
}
// 1. Read map file to find the location of libart.so.
std::vector<ThreadMmap> thread_mmaps;
if (!GetThreadMmapsInProcess(pid_, &thread_mmaps)) {
return false;
}
std::string art_lib_path;
for (auto& map : thread_mmaps) {
if (android::base::EndsWith(map.name, "libart.so")) {
art_lib_path = map.name;
break;
}
}
if (art_lib_path.empty()) {
return false;
}
is_64bit_ = art_lib_path.find("lib64") != std::string::npos;
// 2. Read libart.so to find the addresses of __jit_debug_descriptor and __dex_debug_descriptor.
uint64_t min_vaddr_in_file;
ElfStatus status = ReadMinExecutableVirtualAddressFromElfFile(art_lib_path, BuildId(),
&min_vaddr_in_file);
if (status != ElfStatus::NO_ERROR) {
LOG(ERROR) << "ReadMinExecutableVirtualAddress failed, status = " << status;
return false;
}
uint64_t min_vaddr_in_memory = 0u;
for (auto& map : thread_mmaps) {
if (map.executable && map.name == art_lib_path) {
min_vaddr_in_memory = map.start_addr;
break;
}
}
if (min_vaddr_in_memory == 0u) {
return false;
}
const char* jit_str = "__jit_debug_descriptor";
const char* dex_str = "__dex_debug_descriptor";
uint64_t jit_addr = 0u;
uint64_t dex_addr = 0u;
auto callback = [&](const ElfFileSymbol& symbol) {
if (symbol.name == jit_str) {
jit_addr = symbol.vaddr - min_vaddr_in_file + min_vaddr_in_memory;
} else if (symbol.name == dex_str) {
dex_addr = symbol.vaddr - min_vaddr_in_file + min_vaddr_in_memory;
}
};
if (ParseDynamicSymbolsFromElfFile(art_lib_path, callback) != ElfStatus::NO_ERROR) {
return false;
}
if (jit_addr == 0u || dex_addr == 0u) {
return false;
}
descriptors_addr_ = std::min(jit_addr, dex_addr);
descriptors_size_ = std::max(jit_addr, dex_addr) +
(is_64bit_ ? sizeof(JITDescriptor64) : sizeof(JITDescriptor32)) - descriptors_addr_;
if (descriptors_size_ >= 4096u) {
PLOG(WARNING) << "The descriptors_size is unexpected large: " << descriptors_size_;
}
descriptors_buf_.resize(descriptors_size_);
jit_descriptor_offset_ = jit_addr - descriptors_addr_;
dex_descriptor_offset_ = dex_addr - descriptors_addr_;
initialized_ = true;
return true;
}
bool JITDebugReader::ReadRemoteMem(uint64_t remote_addr, uint64_t size, void* data) {
iovec local_iov;
local_iov.iov_base = data;
local_iov.iov_len = size;
iovec remote_iov;
remote_iov.iov_base = reinterpret_cast<void*>(static_cast<uintptr_t>(remote_addr));
remote_iov.iov_len = size;
ssize_t result = process_vm_readv(pid_, &local_iov, 1, &remote_iov, 1, 0);
if (static_cast<size_t>(result) != size) {
PLOG(DEBUG) << "ReadRemoteMem(" << " pid " << pid_ << ", addr " << std::hex
<< remote_addr << ", size " << size << ") failed";
return false;
}
return true;
}
bool JITDebugReader::ReadDescriptors(Descriptor* jit_descriptor, Descriptor* dex_descriptor) {
if (!ReadRemoteMem(descriptors_addr_, descriptors_size_, descriptors_buf_.data())) {
return false;
}
return LoadDescriptor(descriptors_buf_.data() + jit_descriptor_offset_, jit_descriptor) &&
LoadDescriptor(descriptors_buf_.data() + dex_descriptor_offset_, dex_descriptor);
}
bool JITDebugReader::LoadDescriptor(const char* data, Descriptor* descriptor) {
if (is_64bit_) {
return LoadDescriptorImpl<JITDescriptor64>(data, descriptor);
}
return LoadDescriptorImpl<JITDescriptor32>(data, descriptor);
}
template <typename DescriptorT>
bool JITDebugReader::LoadDescriptorImpl(const char* data, Descriptor* descriptor) {
DescriptorT raw_descriptor;
MoveFromBinaryFormat(raw_descriptor, data);
if (!raw_descriptor.Valid() || sizeof(raw_descriptor) != raw_descriptor.sizeof_descriptor ||
(is_64bit_ ? sizeof(JITCodeEntry64) : sizeof(JITCodeEntry32)) != raw_descriptor.sizeof_entry
) {
return false;
}
descriptor->action_seqlock = raw_descriptor.action_seqlock;
descriptor->action_timestamp = raw_descriptor.action_timestamp;
descriptor->first_entry_addr = raw_descriptor.first_entry_addr;
return true;
}
// Read new code entries with timestamp > last_action_timestamp.
// Since we don't stop the app process while reading code entries, it is possible we are reading
// broken data. So return false once we detect that the data is broken.
bool JITDebugReader::ReadNewCodeEntries(const Descriptor& descriptor,
uint64_t last_action_timestamp,
std::vector<CodeEntry>* new_code_entries) {
if (is_64bit_) {
return ReadNewCodeEntriesImpl<JITDescriptor64, JITCodeEntry64>(descriptor,
last_action_timestamp,
new_code_entries);
}
return ReadNewCodeEntriesImpl<JITDescriptor32, JITCodeEntry32>(descriptor,
last_action_timestamp,
new_code_entries);
}
template <typename DescriptorT, typename CodeEntryT>
bool JITDebugReader::ReadNewCodeEntriesImpl(const Descriptor& descriptor,
uint64_t last_action_timestamp,
std::vector<CodeEntry>* new_code_entries) {
uint64_t current_entry_addr = descriptor.first_entry_addr;
uint64_t prev_entry_addr = 0u;
std::unordered_set<uint64_t> entry_addr_set;
for (size_t i = 0u; i < MAX_LINKLED_LIST_LENGTH && current_entry_addr != 0u; ++i) {
if (entry_addr_set.find(current_entry_addr) != entry_addr_set.end()) {
// We enter a loop, which means a broken linked list.
return false;
}
CodeEntryT entry;
if (!ReadRemoteMem(current_entry_addr, sizeof(entry), &entry)) {
return false;
}
if (entry.prev_addr != prev_entry_addr || !entry.Valid()) {
// A broken linked list
return false;
}
if (entry.register_timestamp <= last_action_timestamp) {
// The linked list has entries with timestamp in decreasing order. So stop searching
// once we hit an entry with timestamp <= last_action_timestmap.
break;
}
CodeEntry code_entry;
code_entry.addr = current_entry_addr;
code_entry.symfile_addr = entry.symfile_addr;
code_entry.symfile_size = entry.symfile_size;
new_code_entries->push_back(code_entry);
entry_addr_set.insert(current_entry_addr);
prev_entry_addr = current_entry_addr;
current_entry_addr = entry.next_addr;
}
return true;
}
void JITDebugReader::ReadJITSymFiles(const std::vector<CodeEntry>& jit_entries,
std::vector<JITSymFile>* jit_symfiles) {
std::vector<char> data;
for (auto& jit_entry : jit_entries) {
if (jit_entry.symfile_size > MAX_JIT_SYMFILE_SIZE) {
continue;
}
if (data.size() < jit_entry.symfile_size) {
data.resize(jit_entry.symfile_size);
}
if (!ReadRemoteMem(jit_entry.symfile_addr, jit_entry.symfile_size, data.data())) {
continue;
}
if (!IsValidElfFileMagic(data.data(), jit_entry.symfile_size)) {
continue;
}
uint64_t min_addr = UINT64_MAX;
uint64_t max_addr = 0;
auto callback = [&](const ElfFileSymbol& symbol) {
min_addr = std::min(min_addr, symbol.vaddr);
max_addr = std::max(max_addr, symbol.vaddr + symbol.len);
LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr
<< " - " << (symbol.vaddr + symbol.len) << " with size " << symbol.len;
};
if (ParseSymbolsFromElfFileInMemory(data.data(), jit_entry.symfile_size, callback) !=
ElfStatus::NO_ERROR || min_addr >= max_addr) {
continue;
}
std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile(!keep_symfiles_);
if (tmp_file == nullptr || !android::base::WriteFully(tmp_file->fd, data.data(),
jit_entry.symfile_size)) {
continue;
}
if (keep_symfiles_) {
tmp_file->DoNotRemove();
}
JITSymFile symfile;
symfile.addr = min_addr;
symfile.len = max_addr - min_addr;
symfile.file_path = tmp_file->path;
jit_symfiles->push_back(symfile);
}
}
void JITDebugReader::ReadDexSymFiles(const std::vector<CodeEntry>& dex_entries,
std::vector<DexSymFile>* dex_symfiles) {
std::vector<ThreadMmap> thread_mmaps;
if (!GetThreadMmapsInProcess(pid_, &thread_mmaps)) {
return;
}
auto comp = [](const ThreadMmap& map, uint64_t addr) {
return map.start_addr <= addr;
};
for (auto& dex_entry : dex_entries) {
auto it = std::lower_bound(thread_mmaps.begin(), thread_mmaps.end(),
dex_entry.symfile_addr, comp);
if (it == thread_mmaps.begin()) {
continue;
}
--it;
if (it->start_addr + it->len < dex_entry.symfile_addr + dex_entry.symfile_size) {
continue;
}
std::string file_path;
std::string zip_path;
std::string entry_path;
if (ParseExtractedInMemoryPath(it->name, &zip_path, &entry_path)) {
file_path = GetUrlInApk(zip_path, entry_path);
} else {
if (!IsRegularFile(it->name)) {
// TODO: read dex file only exist in memory?
continue;
}
file_path = it->name;
}
// Offset of dex file in .vdex file or .apk file.
uint64_t dex_file_offset = dex_entry.symfile_addr - it->start_addr + it->pgoff;
dex_symfiles->emplace_back(dex_file_offset, file_path);
LOG(VERBOSE) << "DexFile " << file_path << "+" << std::hex << dex_file_offset
<< " in map [" << it->start_addr << " - " << (it->start_addr + it->len)
<< "] with size " << dex_entry.symfile_size;
}
}
} // namespace simpleperf