/* * 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 #include #include #include #include #include #include #include #include #include #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 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(magic), "Android1", 8) == 0; } }; // Match the format of JITCodeEntry in art/runtime/jit/debugger_itnerface.cc. template 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 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; using JITDescriptor64 = JITDescriptor; #if defined(__x86_64__) // Make sure simpleperf built for i386 and x86_64 see the correct JITCodeEntry layout of i386. using JITCodeEntry32 = PackedJITCodeEntry; #else using JITCodeEntry32 = JITCodeEntry; #endif using JITCodeEntry64 = JITCodeEntry; // 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::RegisterDebugInfoCallback(IOEventLoop* loop, const debug_info_callback_t& callback) { debug_info_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(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(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(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(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 FlushDebugInfo(record->Timestamp()); } bool JITDebugReader::FlushDebugInfo(uint64_t timestamp) { if (sync_with_records_) { if (!debug_info_q_.empty() && debug_info_q_.top().timestamp < timestamp) { std::vector debug_info; while (!debug_info_q_.empty() && debug_info_q_.top().timestamp < timestamp) { debug_info.emplace_back(debug_info_q_.top()); debug_info_q_.pop(); } return debug_info_callback_(debug_info, false); } } return true; } bool JITDebugReader::ReadAllProcesses() { if (!IOEventLoop::DisableEvent(read_event_)) { return false; } std::vector debug_info; for (auto it = processes_.begin(); it != processes_.end();) { Process& process = it->second; ReadProcess(process, &debug_info); if (process.died) { LOG(DEBUG) << "Stop monitoring process " << process.pid; it = processes_.erase(it); } else { ++it; } } if (!AddDebugInfo(debug_info, 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 debug_info; ReadProcess(it->second, &debug_info); return AddDebugInfo(debug_info, false); } return true; } void JITDebugReader::ReadProcess(Process& process, std::vector* debug_info) { 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_debug_info = [&](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 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) { ReadJITCodeDebugInfo(process, new_entries, debug_info); } else { ReadDexFileDebugInfo(process, new_entries, debug_info); } return true; }; if (read_debug_info(jit_descriptor, process.last_jit_descriptor, true)) { process.last_jit_descriptor = jit_descriptor; } if (read_debug_info(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 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; uint64_t file_offset; ElfStatus status = ReadMinExecutableVirtualAddressFromElfFile(art_lib_path, BuildId(), &min_vaddr_in_file, &file_offset); 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(static_cast(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(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(data, descriptor); } return LoadDescriptorImpl(data, descriptor); } template 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* new_code_entries) { if (process.is_64bit) { return ReadNewCodeEntriesImpl( process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); } return ReadNewCodeEntriesImpl( process, descriptor, last_action_timestamp, read_entry_limit, new_code_entries); } template bool JITDebugReader::ReadNewCodeEntriesImpl(Process& process, const Descriptor& descriptor, uint64_t last_action_timestamp, uint32_t read_entry_limit, std::vector* new_code_entries) { uint64_t current_entry_addr = descriptor.first_entry_addr; uint64_t prev_entry_addr = 0u; std::unordered_set 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; code_entry.timestamp = entry.register_timestamp; 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::ReadJITCodeDebugInfo(Process& process, const std::vector& jit_entries, std::vector* debug_info) { std::vector 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 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(); } debug_info->emplace_back(process.pid, jit_entry.timestamp, min_addr, max_addr - min_addr, tmp_file->path); } } void JITDebugReader::ReadDexFileDebugInfo(Process& process, const std::vector& dex_entries, std::vector* debug_info) { std::vector 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; debug_info->emplace_back(process.pid, dex_entry.timestamp, 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; } } bool JITDebugReader::AddDebugInfo(const std::vector& debug_info, bool sync_kernel_records) { if (!debug_info.empty()) { if (sync_with_records_) { for (auto& info : debug_info) { debug_info_q_.push(std::move(info)); } } else { return debug_info_callback_(debug_info, sync_kernel_records); } } return true; } } // namespace simpleperf