/* * 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 #include #include #include #include "dso.h" #include "environment.h" #include "read_apk.h" #include "read_elf.h" #include "utils.h" using android::base::StringPrintf; 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_interface.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; int AndroidVersion() const { return magic[7] - '0'; } }; // Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc // with JITDescriptor.magic == "Android1". 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 // with JITDescriptor.magic == "Android1". 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; } }; // Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc // with JITDescriptor.magic == "Android2". template struct JITCodeEntryV2 { ADDRT next_addr; ADDRT prev_addr; ADDRT symfile_addr; uint64_t symfile_size; uint64_t register_timestamp; // CLOCK_MONOTONIC time of entry registration uint32_t seqlock; // even value if valid bool Valid() const { return (seqlock & 1) == 0; } }; // Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc // with JITDescriptor.magic == "Android2". template struct __attribute__((packed)) PackedJITCodeEntryV2 { ADDRT next_addr; ADDRT prev_addr; ADDRT symfile_addr; uint64_t symfile_size; uint64_t register_timestamp; uint32_t seqlock; bool Valid() const { return (seqlock & 1) == 0; } }; // Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc // with JITDescriptor.magic == "Android2". template struct __attribute__((packed)) PaddedJITCodeEntryV2 { ADDRT next_addr; ADDRT prev_addr; ADDRT symfile_addr; uint64_t symfile_size; uint64_t register_timestamp; uint32_t seqlock; uint32_t pad; bool Valid() const { return (seqlock & 1) == 0; } }; 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; using JITCodeEntry32V2 = PackedJITCodeEntryV2; #else using JITCodeEntry32 = JITCodeEntry; using JITCodeEntry32V2 = JITCodeEntryV2; #endif using JITCodeEntry64 = JITCodeEntry; #if defined(__i386__) // Make sure simpleperf built for i386 and x86_64 see the correct JITCodeEntry layout of x86_64. using JITCodeEntry64V2 = PaddedJITCodeEntryV2; #else using JITCodeEntry64V2 = JITCodeEntryV2; #endif template bool JITDescriptor::Valid() const { const char* magic_str = reinterpret_cast(magic); if (version != 1 || !(strncmp(magic_str, "Android1", 8) == 0 || strncmp(magic_str, "Android2", 8) == 0)) { return false; } if (sizeof(*this) != sizeof_descriptor) { return false; } if (sizeof(ADDRT) == 4) { return sizeof_entry == (AndroidVersion() == 1) ? sizeof(JITCodeEntry32) : sizeof(JITCodeEntry32V2); } return sizeof_entry == (AndroidVersion() == 1) ? sizeof(JITCodeEntry64) : sizeof(JITCodeEntry64V2); } // 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, ""); static_assert(sizeof(JITCodeEntry32V2) == 32, ""); static_assert(sizeof(JITCodeEntry64) == 40, ""); static_assert(sizeof(JITCodeEntry64V2) == 48, ""); #else static_assert(sizeof(JITCodeEntry32) == 32, ""); static_assert(sizeof(JITCodeEntry32V2) == 40, ""); static_assert(sizeof(JITCodeEntry64) == 40, ""); static_assert(sizeof(JITCodeEntry64V2) == 48, ""); #endif class TempSymFile { public: static std::unique_ptr Create(std::string&& path, bool remove_in_destructor) { FILE* fp = fopen(path.data(), "web"); if (fp == nullptr) { PLOG(ERROR) << "failed to create " << path; return nullptr; } if (remove_in_destructor) { ScopedTempFiles::RegisterTempFile(path); } return std::unique_ptr(new TempSymFile(std::move(path), fp)); } bool WriteEntry(const char* data, size_t size) { if (fwrite(data, size, 1, fp_.get()) != 1) { PLOG(ERROR) << "failed to write to " << path_; return false; } file_offset_ += size; return true; } bool Flush() { if (fflush(fp_.get()) != 0) { PLOG(ERROR) << "failed to flush " << path_; return false; } return true; } const std::string& GetPath() const { return path_; } uint64_t GetOffset() const { return file_offset_; } private: TempSymFile(std::string&& path, FILE* fp) : path_(std::move(path)), fp_(fp, fclose) {} const std::string path_; std::unique_ptr fp_; uint64_t file_offset_ = 0; }; JITDebugReader::JITDebugReader(const std::string& symfile_prefix, SymFileOption symfile_option, SyncOption sync_option) : symfile_prefix_(symfile_prefix), symfile_option_(symfile_option), sync_option_(sync_option) {} JITDebugReader::~JITDebugReader() {} 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_option_ == SyncOption::kSyncWithRecords) { 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; if (!ReadProcess(process, &debug_info)) { return false; } 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; return ReadProcess(it->second, &debug_info) && AddDebugInfo(debug_info, false); } return true; } bool JITDebugReader::ReadProcess(Process& process, std::vector* debug_info) { if (process.died || (!process.initialized && !InitializeProcess(process))) { return true; } // 1. Read descriptors. Descriptor jit_descriptor; Descriptor dex_descriptor; if (!ReadDescriptors(process, &jit_descriptor, &dex_descriptor)) { return true; } // 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 true; } // 3. Read new symfiles. return ReadDebugInfo(process, jit_descriptor, debug_info) && ReadDebugInfo(process, dex_descriptor, debug_info); } bool JITDebugReader::ReadDebugInfo(Process& process, Descriptor& new_descriptor, std::vector* debug_info) { DescriptorType type = new_descriptor.type; Descriptor* old_descriptor = (type == DescriptorType::kJIT) ? &process.last_jit_descriptor : &process.last_dex_descriptor; bool has_update = new_descriptor.action_seqlock != old_descriptor->action_seqlock && (new_descriptor.action_seqlock & 1) == 0; LOG(DEBUG) << (type == DescriptorType::kJIT ? "JIT" : "Dex") << " symfiles of pid " << process.pid << ": old seqlock " << old_descriptor->action_seqlock << ", new seqlock " << new_descriptor.action_seqlock; if (!has_update) { return true; } 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 true; } // If the descriptor was changed while we were reading new entries, skip reading debug info this // time. if (IsDescriptorChanged(process, new_descriptor)) { return true; } LOG(DEBUG) << (type == DescriptorType::kJIT ? "JIT" : "Dex") << " symfiles of pid " << process.pid << ": read " << new_entries.size() << " new entries"; if (!new_entries.empty()) { if (type == DescriptorType::kJIT) { if (!ReadJITCodeDebugInfo(process, new_entries, debug_info)) { return false; } } else { ReadDexFileDebugInfo(process, new_entries, debug_info); } } *old_descriptor = new_descriptor; return true; } bool JITDebugReader::IsDescriptorChanged(Process& process, Descriptor& prev_descriptor) { Descriptor tmp_jit_descriptor; Descriptor tmp_dex_descriptor; if (!ReadDescriptors(process, &tmp_jit_descriptor, &tmp_dex_descriptor)) { return true; } if (prev_descriptor.type == DescriptorType::kJIT) { return prev_descriptor.action_seqlock != tmp_jit_descriptor.action_seqlock; } return prev_descriptor.action_seqlock != tmp_dex_descriptor.action_seqlock; } 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. ElfStatus status; auto elf = ElfFile::Open(art_lib_path, &status); if (!elf) { LOG(ERROR) << "failed to read min_exec_vaddr from " << art_lib_path << ": " << status; return nullptr; } uint64_t file_offset; uint64_t min_vaddr_in_file = elf->ReadMinExecutableVaddr(&file_offset); // min_vaddr_in_file is the min vaddr of executable segments. It may not be page aligned. // And dynamic linker will create map mapping to (segment.p_vaddr & PAGE_MASK). uint64_t aligned_segment_vaddr = min_vaddr_in_file & PAGE_MASK; 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 - aligned_segment_vaddr; } else if (symbol.name == dex_str) { dex_addr = symbol.vaddr - aligned_segment_vaddr; } }; elf->ParseDynamicSymbols(callback); 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; } if (!LoadDescriptor(process.is_64bit, &descriptors_buf_[process.jit_descriptor_offset], jit_descriptor) || !LoadDescriptor(process.is_64bit, &descriptors_buf_[process.dex_descriptor_offset], dex_descriptor)) { return false; } jit_descriptor->type = DescriptorType::kJIT; dex_descriptor->type = DescriptorType::kDEX; return true; } 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()) { 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; descriptor->version = raw_descriptor.AndroidVersion(); 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 (descriptor.version == 1) { 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); } if (descriptor.version == 2) { 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); } return false; } 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; } if (entry.symfile_size > 0) { 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; } bool JITDebugReader::ReadJITCodeDebugInfo(Process& process, const std::vector& jit_entries, std::vector* debug_info) { std::vector data; bool need_flush_app_symfile = false; 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; } if (!app_symfile_) { std::string path = symfile_prefix_ + "_jit_app_cache"; app_symfile_ = TempSymFile::Create(std::move(path), symfile_option_ == SymFileOption::kDropSymFiles); if (!app_symfile_) { return false; } } uint64_t file_offset = app_symfile_->GetOffset(); if (!app_symfile_->WriteEntry(data.data(), jit_entry.symfile_size)) { return false; } need_flush_app_symfile = true; auto callback = [&](const ElfFileSymbol& symbol) { if (symbol.len == 0) { // Some arm labels can have zero length. return; } LOG(VERBOSE) << "JITSymbol " << symbol.name << " at [" << std::hex << symbol.vaddr << " - " << (symbol.vaddr + symbol.len) << " with size " << symbol.len; // Pass out the location of the symfile for unwinding and symbolization. std::string location_in_file = StringPrintf(":%" PRIu64 "-%" PRIu64, file_offset, file_offset + jit_entry.symfile_size); debug_info->emplace_back(process.pid, jit_entry.timestamp, symbol.vaddr, symbol.len, app_symfile_->GetPath() + location_in_file, file_offset); }; ElfStatus status; auto elf = ElfFile::Open(data.data(), jit_entry.symfile_size, &status); if (elf) { elf->ParseSymbols(callback); } } if (need_flush_app_symfile) { if (!app_symfile_->Flush()) { return false; } } return true; } 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; std::shared_ptr extracted_dex_file_map; if (ParseExtractedInMemoryPath(it->name, &zip_path, &entry_path)) { file_path = GetUrlInApk(zip_path, entry_path); extracted_dex_file_map = std::make_shared(*it); } 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, extracted_dex_file_map); 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_option_ == SyncOption::kSyncWithRecords) { 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