| /* |
| * 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 |