| /* | |
| * Copyright (C) 2017 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 <stdint.h> | |
| #include <sys/mman.h> | |
| #include <cstddef> | |
| #include <atomic> | |
| #include <deque> | |
| #include <map> | |
| #include <memory> | |
| #include <unordered_set> | |
| #include <vector> | |
| #include <unwindstack/Elf.h> | |
| #include <unwindstack/JitDebug.h> | |
| #include <unwindstack/Maps.h> | |
| #include <unwindstack/Memory.h> | |
| #if !defined(NO_LIBDEXFILE_SUPPORT) | |
| #include <DexFile.h> | |
| #endif | |
| // This implements the JIT Compilation Interface. | |
| // See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html | |
| namespace unwindstack { | |
| // 32-bit platforms may differ in alignment of uint64_t. | |
| struct Uint64_P { | |
| uint64_t value; | |
| } __attribute__((packed)); | |
| struct Uint64_A { | |
| uint64_t value; | |
| } __attribute__((aligned(8))); | |
| // Wrapper around other memory object which protects us against data races. | |
| // It will check seqlock after every read, and fail if the seqlock changed. | |
| // This ensues that the read memory has not been partially modified. | |
| struct JitMemory : public Memory { | |
| size_t Read(uint64_t addr, void* dst, size_t size) override; | |
| Memory* parent_ = nullptr; | |
| uint64_t seqlock_addr_ = 0; | |
| uint32_t expected_seqlock_ = 0; | |
| bool failed_due_to_race_ = false; | |
| }; | |
| template <typename Symfile> | |
| struct JitCacheEntry { | |
| // PC memory range described by this entry. | |
| uint64_t addr_ = 0; | |
| uint64_t size_ = 0; | |
| std::unique_ptr<Symfile> symfile_; | |
| bool Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t size); | |
| }; | |
| template <typename Symfile, typename PointerT, typename Uint64_T> | |
| class JitDebugImpl : public JitDebug<Symfile>, public Global { | |
| public: | |
| static constexpr const char* kDescriptorExtMagic = "Android1"; | |
| static constexpr int kMaxRaceRetries = 16; | |
| struct JITCodeEntry { | |
| PointerT next; | |
| PointerT prev; | |
| PointerT symfile_addr; | |
| Uint64_T symfile_size; | |
| }; | |
| struct JITDescriptor { | |
| uint32_t version; | |
| uint32_t action_flag; | |
| PointerT relevant_entry; | |
| PointerT first_entry; | |
| }; | |
| // Android-specific extensions. | |
| struct JITDescriptorExt { | |
| JITDescriptor desc; | |
| uint8_t magic[8]; | |
| uint32_t flags; | |
| uint32_t sizeof_descriptor; | |
| uint32_t sizeof_entry; | |
| uint32_t action_seqlock; | |
| uint64_t action_timestamp; | |
| }; | |
| JitDebugImpl(ArchEnum arch, std::shared_ptr<Memory>& memory, | |
| std::vector<std::string>& search_libs) | |
| : Global(memory, search_libs) { | |
| SetArch(arch); | |
| } | |
| Symfile* Get(Maps* maps, uint64_t pc) override; | |
| virtual bool ReadVariableData(uint64_t offset); | |
| virtual void ProcessArch() {} | |
| bool Update(Maps* maps); | |
| bool Read(Maps* maps, JitMemory* memory); | |
| bool initialized_ = false; | |
| uint64_t descriptor_addr_ = 0; // Non-zero if we have found (non-empty) descriptor. | |
| uint64_t seqlock_addr_ = 0; // Re-read entries if the value at this address changes. | |
| uint32_t last_seqlock_ = ~0u; // The value of seqlock when we last read the entries. | |
| std::deque<JitCacheEntry<Symfile>> entries_; | |
| std::mutex lock_; | |
| }; | |
| template <typename Symfile> | |
| std::unique_ptr<JitDebug<Symfile>> JitDebug<Symfile>::Create(ArchEnum arch, | |
| std::shared_ptr<Memory>& memory, | |
| std::vector<std::string> search_libs) { | |
| typedef JitDebugImpl<Symfile, uint32_t, Uint64_P> JitDebugImpl32P; | |
| typedef JitDebugImpl<Symfile, uint32_t, Uint64_A> JitDebugImpl32A; | |
| typedef JitDebugImpl<Symfile, uint64_t, Uint64_A> JitDebugImpl64A; | |
| switch (arch) { | |
| case ARCH_X86: | |
| static_assert(sizeof(typename JitDebugImpl32P::JITCodeEntry) == 20, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl32P::JITDescriptor) == 16, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl32P::JITDescriptorExt) == 48, "layout"); | |
| return std::unique_ptr<JitDebug>(new JitDebugImpl32P(arch, memory, search_libs)); | |
| break; | |
| case ARCH_ARM: | |
| case ARCH_MIPS: | |
| static_assert(sizeof(typename JitDebugImpl32A::JITCodeEntry) == 24, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl32A::JITDescriptor) == 16, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl32A::JITDescriptorExt) == 48, "layout"); | |
| return std::unique_ptr<JitDebug>(new JitDebugImpl32A(arch, memory, search_libs)); | |
| break; | |
| case ARCH_ARM64: | |
| case ARCH_X86_64: | |
| case ARCH_MIPS64: | |
| static_assert(sizeof(typename JitDebugImpl64A::JITCodeEntry) == 32, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl64A::JITDescriptor) == 24, "layout"); | |
| static_assert(sizeof(typename JitDebugImpl64A::JITDescriptorExt) == 56, "layout"); | |
| return std::unique_ptr<JitDebug>(new JitDebugImpl64A(arch, memory, search_libs)); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| } | |
| size_t JitMemory::Read(uint64_t addr, void* dst, size_t size) { | |
| if (!parent_->ReadFully(addr, dst, size)) { | |
| return 0; | |
| } | |
| // This is required for memory synchronization if the we are working with local memory. | |
| // For other types of memory (e.g. remote) this is no-op and has no significant effect. | |
| std::atomic_thread_fence(std::memory_order_acquire); | |
| uint32_t seen_seqlock; | |
| if (!parent_->Read32(seqlock_addr_, &seen_seqlock)) { | |
| return 0; | |
| } | |
| if (seen_seqlock != expected_seqlock_) { | |
| failed_due_to_race_ = true; | |
| return 0; | |
| } | |
| return size; | |
| } | |
| template <typename Symfile, typename PointerT, typename Uint64_T> | |
| bool JitDebugImpl<Symfile, PointerT, Uint64_T>::ReadVariableData(uint64_t addr) { | |
| JITDescriptor desc; | |
| if (!this->memory_->ReadFully(addr, &desc, sizeof(desc))) { | |
| return false; | |
| } | |
| if (desc.version != 1) { | |
| return false; | |
| } | |
| if (desc.first_entry == 0) { | |
| return false; // There could be multiple descriptors. Ignore empty ones. | |
| } | |
| descriptor_addr_ = addr; | |
| JITDescriptorExt desc_ext; | |
| if (this->memory_->ReadFully(addr, &desc_ext, sizeof(desc_ext)) && | |
| memcmp(desc_ext.magic, kDescriptorExtMagic, 8) == 0) { | |
| seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptorExt, action_seqlock); | |
| } else { | |
| // In the absence of Android-specific fields, use the head pointer instead. | |
| seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptor, first_entry); | |
| } | |
| return true; | |
| } | |
| template <typename Symfile> | |
| static const char* GetDescriptorName(); | |
| template <> | |
| const char* GetDescriptorName<Elf>() { | |
| return "__jit_debug_descriptor"; | |
| } | |
| template <typename Symfile, typename PointerT, typename Uint64_T> | |
| Symfile* JitDebugImpl<Symfile, PointerT, Uint64_T>::Get(Maps* maps, uint64_t pc) { | |
| std::lock_guard<std::mutex> guard(lock_); | |
| if (!initialized_) { | |
| FindAndReadVariable(maps, GetDescriptorName<Symfile>()); | |
| initialized_ = true; | |
| } | |
| if (descriptor_addr_ == 0) { | |
| return nullptr; | |
| } | |
| if (!Update(maps)) { | |
| return nullptr; | |
| } | |
| Symfile* fallback = nullptr; | |
| for (auto& entry : entries_) { | |
| // Skip entries which are obviously not relevant (if we know the PC range). | |
| if (entry.size_ == 0 || (entry.addr_ <= pc && (pc - entry.addr_) < entry.size_)) { | |
| // Double check the entry contains the PC in case there are overlapping entries. | |
| // This is might happen for native-code due to GC and for DEX due to data sharing. | |
| std::string method_name; | |
| uint64_t method_offset; | |
| if (entry.symfile_->GetFunctionName(pc, &method_name, &method_offset)) { | |
| return entry.symfile_.get(); | |
| } | |
| fallback = entry.symfile_.get(); // Tests don't have any symbols. | |
| } | |
| } | |
| return fallback; // Not found. | |
| } | |
| // Update JIT entries if needed. It will retry if there are data races. | |
| template <typename Symfile, typename PointerT, typename Uint64_T> | |
| bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Update(Maps* maps) { | |
| // We might need to retry the whole read in the presence of data races. | |
| for (int i = 0; i < kMaxRaceRetries; i++) { | |
| // Read the seqlock (counter which is incremented before and after any modification). | |
| uint32_t seqlock = 0; | |
| if (!this->memory_->Read32(seqlock_addr_, &seqlock)) { | |
| return false; // Failed to read seqlock. | |
| } | |
| // Check if anything changed since the last time we checked. | |
| if (last_seqlock_ != seqlock) { | |
| // Create memory wrapper to allow us to read the entries safely even in a live process. | |
| JitMemory safe_memory; | |
| safe_memory.parent_ = this->memory_.get(); | |
| safe_memory.seqlock_addr_ = seqlock_addr_; | |
| safe_memory.expected_seqlock_ = seqlock; | |
| std::atomic_thread_fence(std::memory_order_acquire); | |
| // Add all entries to our cache. | |
| if (!Read(maps, &safe_memory)) { | |
| if (safe_memory.failed_due_to_race_) { | |
| sleep(0); | |
| continue; // Try again (there was a data race). | |
| } else { | |
| return false; // Proper failure (we could not read the data). | |
| } | |
| } | |
| last_seqlock_ = seqlock; | |
| } | |
| return true; | |
| } | |
| return false; // Too many retries. | |
| } | |
| // Read all JIT entries. It might randomly fail due to data races. | |
| template <typename Symfile, typename PointerT, typename Uint64_T> | |
| bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Read(Maps* maps, JitMemory* memory) { | |
| std::unordered_set<uint64_t> seen_entry_addr; | |
| // Read and verify the descriptor (must be after we have read the initial seqlock). | |
| JITDescriptor desc; | |
| if (!(memory->ReadFully(descriptor_addr_, &desc, sizeof(desc)))) { | |
| return false; | |
| } | |
| entries_.clear(); | |
| JITCodeEntry entry; | |
| for (uint64_t entry_addr = desc.first_entry; entry_addr != 0; entry_addr = entry.next) { | |
| // Check for infinite loops in the lined list. | |
| if (!seen_entry_addr.emplace(entry_addr).second) { | |
| return true; // TODO: Fail when seening infinite loop. | |
| } | |
| // Read the entry (while checking for data races). | |
| if (!memory->ReadFully(entry_addr, &entry, sizeof(entry))) { | |
| return false; | |
| } | |
| // Copy and load the symfile. | |
| entries_.emplace_back(JitCacheEntry<Symfile>()); | |
| if (!entries_.back().Init(maps, memory, entry.symfile_addr, entry.symfile_size.value)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Copy and load ELF file. | |
| template <> | |
| bool JitCacheEntry<Elf>::Init(Maps*, JitMemory* memory, uint64_t addr, uint64_t size) { | |
| // Make a copy of the in-memory symbol file (while checking for data races). | |
| std::unique_ptr<MemoryBuffer> buffer(new MemoryBuffer()); | |
| buffer->Resize(size); | |
| if (!memory->ReadFully(addr, buffer->GetPtr(0), buffer->Size())) { | |
| return false; | |
| } | |
| // Load and validate the ELF file. | |
| symfile_.reset(new Elf(buffer.release())); | |
| symfile_->Init(); | |
| if (!symfile_->valid()) { | |
| return false; | |
| } | |
| symfile_->GetTextRange(&addr_, &size_); | |
| return true; | |
| } | |
| template std::unique_ptr<JitDebug<Elf>> JitDebug<Elf>::Create(ArchEnum, std::shared_ptr<Memory>&, | |
| std::vector<std::string>); | |
| #if !defined(NO_LIBDEXFILE_SUPPORT) | |
| template <> | |
| const char* GetDescriptorName<DexFile>() { | |
| return "__dex_debug_descriptor"; | |
| } | |
| // Copy and load DEX file. | |
| template <> | |
| bool JitCacheEntry<DexFile>::Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t) { | |
| MapInfo* info = maps->Find(addr); | |
| if (info == nullptr) { | |
| return false; | |
| } | |
| symfile_ = DexFile::Create(addr, memory, info); | |
| if (symfile_ == nullptr) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| template std::unique_ptr<JitDebug<DexFile>> JitDebug<DexFile>::Create(ArchEnum, | |
| std::shared_ptr<Memory>&, | |
| std::vector<std::string>); | |
| #endif | |
| } // namespace unwindstack |