| /* |
| * 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/mman.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 { |
| |
| // 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; |
| |
| // It takes about 30us-130us on Pixel (depending on the cpu frequency) to check if the descriptors |
| // have been updated (most time spent in process_vm_preadv). We want to know if the JIT debug info |
| // changed as soon as possible, while not wasting too much time checking for updates. So use a |
| // period of 100 ms. |
| // In system wide profiling, we may need to check JIT debug info changes for many processes, to |
| // avoid spending all time checking, wait 100 ms between any two checks. |
| static constexpr size_t kUpdateJITDebugInfoIntervalInMs = 100; |
| |
| // 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, ""); |
| |
| bool JITDebugReader::RegisterSymFileCallback(IOEventLoop* loop, |
| const symfile_callback_t& callback) { |
| symfile_callback_ = callback; |
| read_event_ = loop->AddPeriodicEvent(SecondToTimeval(kUpdateJITDebugInfoIntervalInMs / 1000.0), |
| [this]() { return ReadAllProcesses(); }); |
| return (read_event_ != nullptr && IOEventLoop::DisableEvent(read_event_)); |
| } |
| |
| bool JITDebugReader::MonitorProcess(pid_t pid) { |
| if (processes_.find(pid) == processes_.end()) { |
| processes_[pid].pid = pid; |
| LOG(DEBUG) << "Start monitoring process " << pid; |
| if (processes_.size() == 1u) { |
| if (!IOEventLoop::EnableEvent(read_event_)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| static bool IsArtLib(const std::string& filename) { |
| return android::base::EndsWith(filename, "libart.so") || |
| android::base::EndsWith(filename, "libartd.so"); |
| } |
| |
| bool JITDebugReader::UpdateRecord(const Record* record) { |
| if (record->type() == PERF_RECORD_MMAP) { |
| auto r = static_cast<const MmapRecord*>(record); |
| if (IsArtLib(r->filename)) { |
| pids_with_art_lib_.emplace(r->data->pid, false); |
| } |
| } else if (record->type() == PERF_RECORD_MMAP2) { |
| auto r = static_cast<const Mmap2Record*>(record); |
| if (IsArtLib(r->filename)) { |
| pids_with_art_lib_.emplace(r->data->pid, false); |
| } |
| } else if (record->type() == PERF_RECORD_FORK) { |
| auto r = static_cast<const ForkRecord*>(record); |
| if (r->data->pid != r->data->ppid && |
| pids_with_art_lib_.find(r->data->ppid) != pids_with_art_lib_.end()) { |
| pids_with_art_lib_.emplace(r->data->pid, false); |
| } |
| } else if (record->type() == PERF_RECORD_SAMPLE) { |
| auto r = static_cast<const SampleRecord*>(record); |
| auto it = pids_with_art_lib_.find(r->tid_data.pid); |
| if (it != pids_with_art_lib_.end() && !it->second) { |
| it->second = true; |
| if (!MonitorProcess(r->tid_data.pid)) { |
| return false; |
| } |
| return ReadProcess(r->tid_data.pid); |
| } |
| } |
| return true; |
| } |
| |
| bool JITDebugReader::ReadAllProcesses() { |
| if (!IOEventLoop::DisableEvent(read_event_)) { |
| return false; |
| } |
| std::vector<JITSymFile> jit_symfiles; |
| std::vector<DexSymFile> dex_symfiles; |
| for (auto it = processes_.begin(); it != processes_.end();) { |
| Process& process = it->second; |
| ReadProcess(process, &jit_symfiles, &dex_symfiles); |
| if (process.died) { |
| LOG(DEBUG) << "Stop monitoring process " << process.pid; |
| it = processes_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| if (!jit_symfiles.empty() || !dex_symfiles.empty()) { |
| if (!symfile_callback_(jit_symfiles, dex_symfiles, true)) { |
| return false; |
| } |
| } |
| if (!processes_.empty()) { |
| return IOEventLoop::EnableEvent(read_event_); |
| } |
| return true; |
| } |
| |
| bool JITDebugReader::ReadProcess(pid_t pid) { |
| auto it = processes_.find(pid); |
| if (it != processes_.end()) { |
| std::vector<JITSymFile> jit_symfiles; |
| std::vector<DexSymFile> dex_symfiles; |
| ReadProcess(it->second, &jit_symfiles, &dex_symfiles); |
| if (!jit_symfiles.empty() || !dex_symfiles.empty()) { |
| return symfile_callback_(jit_symfiles, dex_symfiles, false); |
| } |
| } |
| return true; |
| } |
| |
| void JITDebugReader::ReadProcess(Process& process, std::vector<JITSymFile>* jit_symfiles, |
| std::vector<DexSymFile>* dex_symfiles) { |
| if (process.died || (!process.initialized && !InitializeProcess(process))) { |
| return; |
| } |
| // 1. Read descriptors. |
| Descriptor jit_descriptor; |
| Descriptor dex_descriptor; |
| if (!ReadDescriptors(process, &jit_descriptor, &dex_descriptor)) { |
| return; |
| } |
| // 2. Return if descriptors are not changed. |
| if (jit_descriptor.action_seqlock == process.last_jit_descriptor.action_seqlock && |
| dex_descriptor.action_seqlock == process.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(process, &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; |
| LOG(DEBUG) << (is_jit ? "JIT" : "Dex") << " symfiles of pid " << process.pid |
| << ": old seqlock " << old_descriptor.action_seqlock |
| << ", new seqlock " << new_descriptor.action_seqlock; |
| if (!has_update) { |
| return false; |
| } |
| std::vector<CodeEntry> new_entries; |
| // Adding or removing one code entry will make two increments of action_seqlock. So we should |
| // not read more than (seqlock_diff / 2) new entries. |
| uint32_t read_entry_limit = (new_descriptor.action_seqlock - old_descriptor.action_seqlock) / 2; |
| if (!ReadNewCodeEntries(process, new_descriptor, old_descriptor.action_timestamp, |
| read_entry_limit, &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; |
| } |
| LOG(DEBUG) << (is_jit ? "JIT" : "Dex") << " symfiles of pid " << process.pid |
| << ": read " << new_entries.size() << " new entries"; |
| if (new_entries.empty()) { |
| return true; |
| } |
| if (is_jit) { |
| ReadJITSymFiles(process, new_entries, jit_symfiles); |
| } else { |
| ReadDexSymFiles(process, new_entries, dex_symfiles); |
| } |
| return true; |
| }; |
| if (read_new_symfiles(jit_descriptor, process.last_jit_descriptor, true)) { |
| process.last_jit_descriptor = jit_descriptor; |
| } |
| if (read_new_symfiles(dex_descriptor, process.last_dex_descriptor, false)) { |
| process.last_dex_descriptor = dex_descriptor; |
| } |
| } |
| |
| bool JITDebugReader::InitializeProcess(Process& process) { |
| // 1. Read map file to find the location of libart.so. |
| std::vector<ThreadMmap> thread_mmaps; |
| if (!GetThreadMmapsInProcess(process.pid, &thread_mmaps)) { |
| process.died = true; |
| return false; |
| } |
| std::string art_lib_path; |
| uint64_t min_vaddr_in_memory; |
| for (auto& map : thread_mmaps) { |
| if ((map.prot & PROT_EXEC) && IsArtLib(map.name)) { |
| art_lib_path = map.name; |
| min_vaddr_in_memory = map.start_addr; |
| break; |
| } |
| } |
| if (art_lib_path.empty()) { |
| return false; |
| } |
| process.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. |
| const DescriptorsLocation* location = GetDescriptorsLocation(art_lib_path, process.is_64bit); |
| if (location == nullptr) { |
| return false; |
| } |
| process.descriptors_addr = location->relative_addr + min_vaddr_in_memory; |
| process.descriptors_size = location->size; |
| process.jit_descriptor_offset = location->jit_descriptor_offset; |
| process.dex_descriptor_offset = location->dex_descriptor_offset; |
| process.initialized = true; |
| return true; |
| } |
| |
| const JITDebugReader::DescriptorsLocation* JITDebugReader::GetDescriptorsLocation( |
| const std::string& art_lib_path, bool is_64bit) { |
| auto it = descriptors_location_cache_.find(art_lib_path); |
| if (it != descriptors_location_cache_.end()) { |
| return it->second.relative_addr == 0u ? nullptr : &it->second; |
| } |
| DescriptorsLocation& location = descriptors_location_cache_[art_lib_path]; |
| |
| // 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 nullptr; |
| } |
| 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; |
| } else if (symbol.name == dex_str) { |
| dex_addr = symbol.vaddr - min_vaddr_in_file; |
| } |
| }; |
| if (ParseDynamicSymbolsFromElfFile(art_lib_path, callback) != ElfStatus::NO_ERROR) { |
| return nullptr; |
| } |
| if (jit_addr == 0u || dex_addr == 0u) { |
| return nullptr; |
| } |
| location.relative_addr = std::min(jit_addr, dex_addr); |
| location.size = std::max(jit_addr, dex_addr) + |
| (is_64bit ? sizeof(JITDescriptor64) : sizeof(JITDescriptor32)) - location.relative_addr; |
| if (location.size >= 4096u) { |
| PLOG(WARNING) << "The descriptors_size is unexpected large: " << location.size; |
| } |
| if (descriptors_buf_.size() < location.size) { |
| descriptors_buf_.resize(location.size); |
| } |
| location.jit_descriptor_offset = jit_addr - location.relative_addr; |
| location.dex_descriptor_offset = dex_addr - location.relative_addr; |
| return &location; |
| } |
| |
| bool JITDebugReader::ReadRemoteMem(Process& process, 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(process.pid, &local_iov, 1, &remote_iov, 1, 0); |
| if (static_cast<size_t>(result) != size) { |
| PLOG(DEBUG) << "ReadRemoteMem(" << " pid " << process.pid << ", addr " << std::hex |
| << remote_addr << ", size " << size << ") failed"; |
| process.died = true; |
| return false; |
| } |
| return true; |
| } |
| |
| bool JITDebugReader::ReadDescriptors(Process& process, Descriptor* jit_descriptor, |
| Descriptor* dex_descriptor) { |
| if (!ReadRemoteMem(process, process.descriptors_addr, process.descriptors_size, |
| descriptors_buf_.data())) { |
| return false; |
| } |
| return LoadDescriptor(process.is_64bit, &descriptors_buf_[process.jit_descriptor_offset], |
| jit_descriptor) && |
| LoadDescriptor(process.is_64bit, &descriptors_buf_[process.dex_descriptor_offset], |
| dex_descriptor); |
| } |
| |
| bool JITDebugReader::LoadDescriptor(bool is_64bit, const char* data, Descriptor* descriptor) { |
| if (is_64bit) { |
| return LoadDescriptorImpl<JITDescriptor64, JITCodeEntry64>(data, descriptor); |
| } |
| return LoadDescriptorImpl<JITDescriptor32, JITCodeEntry32>(data, descriptor); |
| } |
| |
| template <typename DescriptorT, typename CodeEntryT> |
| 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 || |
| sizeof(CodeEntryT) != 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(Process& process, const Descriptor& descriptor, |
| uint64_t last_action_timestamp, uint32_t read_entry_limit, |
| std::vector<CodeEntry>* new_code_entries) { |
| if (process.is_64bit) { |
| return ReadNewCodeEntriesImpl<JITDescriptor64, JITCodeEntry64>( |
| process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); |
| } |
| return ReadNewCodeEntriesImpl<JITDescriptor32, JITCodeEntry32>( |
| process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); |
| } |
| |
| template <typename DescriptorT, typename CodeEntryT> |
| bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor& descriptor, |
| uint64_t last_action_timestamp, |
| uint32_t read_entry_limit, |
| 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 < read_entry_limit && 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(process, 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(Process& process, 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(process, 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(); |
| } |
| jit_symfiles->emplace_back(process.pid, min_addr, max_addr - min_addr, tmp_file->path); |
| } |
| } |
| |
| void JITDebugReader::ReadDexSymFiles(Process& process, const std::vector<CodeEntry>& dex_entries, |
| std::vector<DexSymFile>* dex_symfiles) { |
| std::vector<ThreadMmap> thread_mmaps; |
| if (!GetThreadMmapsInProcess(process.pid, &thread_mmaps)) { |
| process.died = true; |
| 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 |