| /* |
| * Copyright (C) 2025 Igalia, S.L. All rights reserved. |
| * Copyright 2010 the V8 project authors. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| #include "config.h" |
| #include "GdbJIT.h" |
| |
| #include <wtf/TZoneMallocInlines.h> |
| |
| #if ENABLE(ASSEMBLER) |
| #if OS(DARWIN) || OS(LINUX) |
| |
| #include "CallFrame.h" |
| #include "CallFrameInlines.h" |
| #include "Options.h" |
| #include "ProfilerSupport.h" |
| #include <array> |
| #include <fcntl.h> |
| #include <mutex> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <wtf/DataLog.h> |
| #include <wtf/MonotonicTime.h> |
| #include <wtf/PageBlock.h> |
| #include <wtf/ProcessID.h> |
| #include <wtf/RefCountedAndCanMakeWeakPtr.h> |
| #include <wtf/StringPrintStream.h> |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN |
| |
| // Binary GDB JIT Interface as described in |
| // http://sourceware.org/gdb/onlinedocs/gdb/Declarations.html |
| extern "C" { |
| enum JITAction { |
| NoAction = 0, |
| RegisterFunction = 1, |
| UnregisterFunction = 2 |
| }; |
| |
| struct JITCodeEntry { |
| JITCodeEntry* next { }; |
| JITCodeEntry* prev { }; |
| uint8_t* symfileAddr { }; |
| uint64_t symfileSize { }; |
| }; |
| |
| struct JITDescriptor { |
| uint32_t version { }; |
| uint32_t actionFlag { }; |
| JITCodeEntry* relevantEntry { }; |
| JITCodeEntry* firstEntry { }; |
| }; |
| |
| // GDB will place breakpoint into this function. |
| // To prevent GCC from inlining or removing it we place noinline attribute |
| // and inline assembler statement inside. |
| static REFERENCED_FROM_ASM NEVER_INLINE void __jit_debug_register_code() |
| { |
| __asm__(""); |
| } |
| |
| // GDB will inspect contents of this descriptor. |
| // Static initialization is necessary to prevent GDB from seeing |
| // uninitialized descriptor. |
| static REFERENCED_FROM_ASM JITDescriptor __jit_debug_descriptor = { 1, 0, nullptr, nullptr }; |
| |
| } // extern "C" |
| |
| namespace JSC { |
| |
| namespace GdbJITInternal { |
| static constexpr bool verbose = false; |
| } // namespace GdbJITInternal |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(GdbJIT); |
| |
| GdbJIT& GdbJIT::singleton() |
| { |
| static LazyNeverDestroyed<GdbJIT> logger; |
| static std::once_flag onceKey; |
| std::call_once(onceKey, [] { |
| logger.construct(); |
| }); |
| return logger.get(); |
| } |
| |
| #if OS(DARWIN) |
| class MachO; |
| class MachOSection; |
| using DebugObject = MachO; |
| using DebugSection = MachOSection; |
| #else |
| class ELF; |
| class ELFSection; |
| class ELFStringTable; |
| using DebugObject = ELF; |
| using DebugSection = ELFSection; |
| #endif |
| |
| template<typename V> |
| static inline void writeUnalignedValue(uint8_t* p, V value) |
| { |
| memcpy(p, &value, sizeof(V)); |
| } |
| |
| class Writer : public RefCountedAndCanMakeWeakPtr<Writer> { |
| public: |
| static Ref<Writer> create(DebugObject* obj) |
| { |
| return adoptRef(*new Writer(obj)); |
| } |
| |
| uintptr_t position() const { return m_position; } |
| |
| template<typename T> |
| class Slot { |
| public: |
| Slot(WeakPtr<Writer> writer, uintptr_t offset) |
| : m_writer(writer) |
| , m_offset(offset) |
| { |
| } |
| |
| T* operator->() { return m_writer->template rawSlotAt<T>(m_offset); } |
| T operator*() { return *m_writer->template rawSlotAt<T>(m_offset); } |
| |
| void set(const T& value) |
| { |
| writeUnalignedValue(m_writer->template addressAt<T>(m_offset), value); |
| } |
| |
| Slot<T> at(int i) { return Slot<T>(m_writer, m_offset + sizeof(T) * i); } |
| |
| private: |
| WeakPtr<Writer> m_writer; |
| uintptr_t m_offset; |
| }; |
| |
| template<typename T> |
| void write(const T& val) |
| { |
| ensure(m_position + sizeof(T)); |
| writeUnalignedValue(addressAt<T>(m_position), val); |
| m_position += sizeof(T); |
| } |
| |
| template<typename T> |
| Slot<T> slotAt(uintptr_t offset) LIFETIME_BOUND |
| { |
| ensure(offset + sizeof(T)); |
| return Slot<T>(*this, offset); |
| } |
| |
| template<typename T> |
| Slot<T> createSlotHere() LIFETIME_BOUND |
| { |
| return createSlotsHere<T>(1); |
| } |
| |
| template<typename T> |
| Slot<T> createSlotsHere(uint32_t count) LIFETIME_BOUND |
| { |
| uintptr_t slotPosition = m_position; |
| m_position += sizeof(T) * count; |
| ensure(m_position); |
| return slotAt<T>(slotPosition); |
| } |
| |
| void ensure(uintptr_t pos) |
| { |
| if (m_buffer.size() < pos) |
| m_buffer.grow(pos); |
| } |
| |
| DebugObject* debugObject() { return m_debugObject; } |
| |
| uint8_t* buffer() LIFETIME_BOUND { return &m_buffer[0]; } |
| |
| void align(uintptr_t align) |
| { |
| uintptr_t delta = m_position % align; |
| if (!delta) |
| return; |
| uintptr_t padding = align - delta; |
| ensure(m_position += padding); |
| ASSERT(m_position % align == 0); |
| } |
| |
| void writeULEB128(uintptr_t value) |
| { |
| do { |
| uint8_t byte = value & 0x7F; |
| value >>= 7; |
| if (value) |
| byte |= 0x80; |
| write<uint8_t>(byte); |
| } while (value); |
| } |
| |
| void writeSLEB128(intptr_t value) |
| { |
| bool more = true; |
| while (more) { |
| int8_t byte = value & 0x7F; |
| bool byteSign = byte & 0x40; |
| value >>= 7; |
| |
| if ((!value && !byteSign) || (value == -1 && byteSign)) |
| more = false; |
| else |
| byte |= 0x80; |
| |
| write<int8_t>(byte); |
| } |
| } |
| |
| void writeString(const CString& str) |
| { |
| for (auto c : str.span()) |
| write<char>(c); |
| write<char>('\0'); |
| } |
| |
| private: |
| template<typename T> |
| friend class Slot; |
| |
| template<typename T> |
| uint8_t* addressAt(uintptr_t offset) LIFETIME_BOUND |
| { |
| ASSERT(offset < m_buffer.size() && offset + sizeof(T) <= m_buffer.size()); |
| return &m_buffer[offset]; |
| } |
| |
| template<typename T> |
| T* rawSlotAt(uintptr_t offset) LIFETIME_BOUND |
| { |
| ASSERT(offset < m_buffer.size() && offset + sizeof(T) <= m_buffer.size()); |
| return reinterpret_cast<T*>(&m_buffer[offset]); |
| } |
| |
| Writer(DebugObject* debugObject) |
| : m_debugObject(debugObject) |
| , m_position(0) |
| { |
| } |
| |
| DebugObject* m_debugObject; |
| uintptr_t m_position; |
| Vector<uint8_t> m_buffer; |
| }; |
| |
| class CodeDescription : public RefCounted<CodeDescription> { |
| public: |
| const CString& name() const LIFETIME_BOUND { return m_name; } |
| |
| const void* codeStart() const { return reinterpret_cast<const void*>(m_codeRegion.data()); } |
| |
| const void* codeEnd() const { return reinterpret_cast<const void*>(m_codeRegion.data() + m_codeRegion.size()); } |
| |
| uintptr_t codeSize() const { return m_codeRegion.size(); } |
| |
| std::span<const uint8_t> region() { return m_codeRegion; } |
| |
| static Ref<CodeDescription> create(const CString& name, std::span<const uint8_t> region) |
| { |
| return adoptRef(*new CodeDescription(name, region)); |
| } |
| |
| private: |
| CodeDescription(const CString& name, std::span<const uint8_t> region) |
| : m_name(name) |
| , m_codeRegion(region) |
| { |
| } |
| |
| CString m_name; |
| std::span<const uint8_t> m_codeRegion; |
| }; |
| |
| static JITCodeEntry* createCodeEntry(uint8_t* symfileAddr, uintptr_t symfileSize) |
| { |
| // It is easiest to just leak this. |
| auto* entry = static_cast<JITCodeEntry*>(malloc(sizeof(JITCodeEntry) + symfileSize)); |
| |
| entry->symfileAddr = reinterpret_cast<uint8_t*>(entry + 1); |
| entry->symfileSize = symfileSize; |
| memcpy(entry->symfileAddr, symfileAddr, symfileSize); |
| |
| entry->prev = entry->next = nullptr; |
| |
| return entry; |
| } |
| |
| static void registerCodeEntry(JITCodeEntry* entry) |
| { |
| entry->next = __jit_debug_descriptor.firstEntry; |
| if (entry->next) |
| entry->next->prev = entry; |
| __jit_debug_descriptor.firstEntry = __jit_debug_descriptor.relevantEntry = entry; |
| |
| __jit_debug_descriptor.actionFlag = RegisterFunction; |
| __jit_debug_register_code(); |
| } |
| |
| static void unregisterCodeEntry(JITCodeEntry* entry) |
| { |
| if (entry->prev) |
| entry->prev->next = entry->next; |
| else |
| __jit_debug_descriptor.firstEntry = entry->next; |
| |
| if (entry->next) |
| entry->next->prev = entry->prev; |
| |
| __jit_debug_descriptor.relevantEntry = entry; |
| __jit_debug_descriptor.actionFlag = UnregisterFunction; |
| __jit_debug_register_code(); |
| } |
| |
| template <typename THeader> |
| class DebugSectionBase { |
| WTF_DEPRECATED_MAKE_FAST_ALLOCATED(DebugSectionBase); |
| |
| public: |
| virtual ~DebugSectionBase() = default; |
| |
| virtual void writeBody(Writer::Slot<THeader> header, Ref<Writer> writer) |
| { |
| uint64_t start = writer->position(); |
| if (writeBodyInternal(writer)) { |
| header->offset = static_cast<uint32_t>(start); |
| uint64_t end = writer->position(); |
| header->size = std::max(end - start, static_cast<uint64_t>(header->size)); |
| } |
| } |
| |
| virtual bool writeBodyInternal(Ref<Writer>) { return false; } |
| |
| using Header = THeader; |
| }; |
| |
| struct MachOSectionHeader { |
| char sectname[16]; |
| char segname[16]; |
| uint64_t addr; |
| uint64_t size; |
| uint32_t offset; |
| uint32_t align; |
| uint32_t reloff; |
| uint32_t nreloc; |
| uint32_t flags; |
| uint32_t reserved1; |
| uint32_t reserved2; |
| uint32_t reserved3; |
| } __attribute__((packed,aligned(1))); |
| |
| class MachOSection : public DebugSectionBase<MachOSectionHeader> { |
| public: |
| enum Type { |
| Regular = 0x0u, |
| AttrCoalesced = 0xBu, |
| AttrSomeInstructions = 0x400u, |
| AttrDebug = 0x02000000u, |
| AttrPureInstructions = 0x80000000u |
| }; |
| |
| MachOSection(const CString& name, const CString& segment, uint32_t align, const void* addr, size_t size, uint32_t flags) |
| : m_name(name) |
| , m_segment(segment) |
| , m_align(align) |
| , m_addr(addr) |
| , m_size(size) |
| , m_flags(flags) |
| { |
| if (m_align) { |
| ASSERT(WTF::isPowerOfTwo(align)); |
| m_align = WTF::fastLog2(align); |
| } |
| } |
| |
| ~MachOSection() override = default; |
| |
| virtual void populateHeader(Writer::Slot<Header> header) |
| { |
| header->addr = reinterpret_cast<uintptr_t>(m_addr); |
| header->size = m_size; |
| header->offset = 0; |
| header->align = m_align; |
| header->reloff = 0; |
| header->nreloc = 0; |
| header->flags = m_flags; |
| header->reserved1 = 0; |
| header->reserved2 = 0; |
| header->reserved3 = 0; |
| memset(header->sectname, 0, sizeof(header->sectname)); |
| memset(header->segname, 0, sizeof(header->segname)); |
| ASSERT(m_name.length() < sizeof(header->sectname)); |
| ASSERT(m_segment.length() < sizeof(header->segname)); |
| strncpy(header->sectname, m_name.data(), sizeof(header->sectname)); |
| strncpy(header->segname, m_segment.data(), sizeof(header->segname)); |
| } |
| |
| const void* addr() const { return m_addr; } |
| size_t size() const { return m_size; } |
| |
| private: |
| CString m_name; |
| CString m_segment; |
| uint32_t m_align; |
| const void* m_addr; |
| size_t m_size; |
| uint32_t m_flags; |
| }; |
| |
| struct ELFsectionHeader { |
| uint32_t name; |
| uint32_t type; |
| uintptr_t flags; |
| const void* address; |
| uintptr_t offset; |
| uintptr_t size; |
| uint32_t link; |
| uint32_t info; |
| uintptr_t alignment; |
| uintptr_t entrySize; |
| } __attribute__((packed,aligned(1))); |
| |
| #if OS(LINUX) |
| class ELFSection : public DebugSectionBase<ELFsectionHeader> { |
| public: |
| enum Type { |
| TypeNull = 0, |
| TypeProgBits = 1, |
| TypeSymTab = 2, |
| TypeStrTab = 3, |
| TypeRela = 4, |
| TypeHash = 5, |
| TypeDynamic = 6, |
| TypeNote = 7, |
| TypeNoBits = 8, |
| TypeRel = 9, |
| TypeShLib = 10, |
| TypeDynSym = 11, |
| TypeLoProc = 0x70000000, |
| TypeX86_64Unwind = 0x70000001, |
| TypeHiProc = 0x7FFFFFFF, |
| TypeLoUser = 0x80000000, |
| TypeHiUser = 0xFFFFFFFF |
| }; |
| |
| enum Flags { |
| FlagWrite = 1, |
| FlagAlloc = 2, |
| FlagExec = 4 |
| }; |
| |
| enum SpecialIndexes { |
| IndexAbsolute = 0xFFF1 |
| }; |
| |
| ELFSection(const CString& name, Type type, uintptr_t align) |
| : m_type(type) |
| , m_name(name) |
| , m_align(align) |
| { |
| } |
| |
| ~ELFSection() override = default; |
| |
| void populateHeader(Writer::Slot<Header>, ELFStringTable* strtab); |
| |
| void writeBody(Writer::Slot<Header> header, Ref<Writer> writer) override |
| { |
| uintptr_t start = writer->position(); |
| if (writeBodyInternal(writer)) { |
| uintptr_t end = writer->position(); |
| header->offset = start; |
| header->size = end - start; |
| } |
| } |
| |
| bool writeBodyInternal(Ref<Writer>) override { return false; } |
| |
| uint16_t index() const { return m_index; } |
| void setIndex(uint16_t index) { m_index = index; } |
| |
| const Type m_type; |
| |
| protected: |
| virtual void populateHeader(Writer::Slot<Header> header) |
| { |
| header->flags = 0; |
| header->address = 0; |
| header->offset = 0; |
| header->size = 0; |
| header->link = 0; |
| header->info = 0; |
| header->entrySize = 0; |
| } |
| |
| private: |
| CString m_name; |
| uintptr_t m_align; |
| uint16_t m_index; |
| }; |
| #endif // OS(LINUX) |
| |
| #if OS(DARWIN) |
| class MachOTextSection : public MachOSection { |
| public: |
| MachOTextSection(uint32_t align, const void* codeAddr, uintptr_t codeSize) |
| : MachOSection("__text", "__TEXT", align, codeAddr, codeSize, MachOSection::Regular | MachOSection::AttrSomeInstructions | MachOSection::AttrPureInstructions) |
| , m_codeAddr(reinterpret_cast<const uint64_t*>(codeAddr)) |
| , m_codeSize(codeSize) |
| { |
| } |
| |
| bool writeBodyInternal(Ref<Writer> writer) override |
| { |
| for (auto* ptr = m_codeAddr; ptr < m_codeAddr + m_codeSize / sizeof(uint64_t); ++ptr) |
| writer->write<uint64_t>(*ptr); |
| return true; |
| } |
| |
| private: |
| const uint64_t* m_codeAddr; |
| const size_t m_codeSize; |
| }; |
| #endif // OS(DARWIN) |
| |
| #if OS(LINUX) |
| class FullHeaderELFSection : public ELFSection { |
| public: |
| FullHeaderELFSection(const CString& name, Type type, uintptr_t align, const void* addr, uintptr_t offset, uintptr_t size, uintptr_t flags) |
| : ELFSection(name, type, align) |
| , m_addr(addr) |
| , m_offset(offset) |
| , m_size(size) |
| , m_flags(flags) |
| { |
| } |
| |
| protected: |
| void populateHeader(Writer::Slot<Header> header) override |
| { |
| ELFSection::populateHeader(header); |
| header->address = m_addr; |
| header->offset = m_offset; |
| header->size = m_size; |
| header->flags = m_flags; |
| } |
| |
| private: |
| const void* m_addr; |
| uintptr_t m_offset; |
| uintptr_t m_size; |
| uintptr_t m_flags; |
| }; |
| |
| class ELFStringTable : public ELFSection { |
| public: |
| explicit ELFStringTable(const CString& name) |
| : ELFSection(name, TypeStrTab, 1) |
| , m_writer(nullptr) |
| , m_offset(0) |
| , m_size(0) |
| { |
| } |
| |
| uintptr_t add(const CString& str) |
| { |
| if (!str.length()) |
| return 0; |
| |
| uintptr_t offset = m_size; |
| writeString(str); |
| return offset; |
| } |
| |
| void attachWriter(Ref<Writer> w) |
| { |
| m_writer = w.ptr(); |
| m_offset = m_writer->position(); |
| |
| // First entry in the string table should be an empty string. |
| writeString(""); |
| } |
| |
| void detachWriter() |
| { |
| m_writer = nullptr; |
| } |
| |
| void writeBody(Writer::Slot<Header> header, Ref<Writer>) override |
| { |
| ASSERT(!m_writer); |
| header->offset = m_offset; |
| header->size = m_size; |
| } |
| |
| private: |
| void writeString(const CString& str) |
| { |
| for (auto c : str.span()) |
| m_writer->write(c); |
| m_writer->write('\0'); |
| m_size += str.length() + 1; |
| } |
| |
| RefPtr<Writer> m_writer; |
| uintptr_t m_offset; |
| uintptr_t m_size; |
| }; |
| |
| void ELFSection::populateHeader(Writer::Slot<ELFSection::Header> header, ELFStringTable* strtab) |
| { |
| header->name = static_cast<uint32_t>(strtab->add(m_name)); |
| header->type = m_type; |
| header->alignment = m_align; |
| populateHeader(header); |
| } |
| #endif // OS(LINUX) |
| |
| #if OS(DARWIN) |
| class MachO { |
| WTF_DEPRECATED_MAKE_FAST_ALLOCATED(MachO); |
| |
| public: |
| size_t addSection(std::unique_ptr<MachOSection> section) |
| { |
| m_sections.append(WTFMove(section)); |
| return m_sections.size() - 1; |
| } |
| |
| void write(Ref<Writer> w, const CString& name, uintptr_t codeStart, uintptr_t) |
| { |
| Writer::Slot<MachOHeader> header = writeHeader(w); |
| uintptr_t loadCommandStart = w->position(); |
| |
| Vector<Writer::Slot<MachOSegmentCommand>> segmentCommands; |
| Vector<Writer::Slot<MachOSection::Header>> sectionHeadersForSegments; |
| |
| for (auto& section : m_sections) { |
| segmentCommands.append(writeSegmentCommand(w, reinterpret_cast<uintptr_t>(section->addr()), section->size())); |
| sectionHeadersForSegments.append(w->createSlotHere<MachOSection::Header>()); |
| } |
| |
| auto symtabCmd = writeSymtabCommand(w, name); |
| header->sizeOfCommands = static_cast<uint32_t>(w->position() - loadCommandStart); |
| |
| for (unsigned i = 0; i < m_sections.size(); ++i) { |
| auto segmentCmd = segmentCommands[i]; |
| auto sectionHeader = sectionHeadersForSegments[i]; |
| segmentCmd->fileOff = w->position(); |
| m_sections[i]->populateHeader(sectionHeader); |
| m_sections[i]->writeBody(sectionHeader, w); |
| segmentCmd->fileSize = w->position() - segmentCmd->fileOff; |
| segmentCmd->vmSize = sectionHeader->size; |
| } |
| |
| writeNList(w, symtabCmd, codeStart); |
| writeStringTable(w, name, symtabCmd); |
| } |
| |
| private: |
| struct MachOHeader { |
| uint32_t magic; |
| uint32_t cpuType; |
| uint32_t cpuSubtype; |
| uint32_t fileType; |
| uint32_t numCommands; |
| uint32_t sizeOfCommands; |
| uint32_t flags; |
| #if USE(JSVALUE64) |
| uint32_t reserved; |
| #endif |
| } __attribute__((packed,aligned(1))); |
| |
| struct MachOSegmentCommand { |
| uint32_t cmd; |
| uint32_t cmdSize; |
| char segname[16]; |
| uint64_t vmAddr; |
| uint64_t vmSize; |
| uint64_t fileOff; |
| uint64_t fileSize; |
| uint32_t maxProt; |
| uint32_t initProt; |
| uint32_t numSects; |
| uint32_t flags; |
| } __attribute__((packed,aligned(1))); |
| |
| struct MachOSymtabCommand { |
| uint32_t cmd; |
| uint32_t cmdSize; |
| uint32_t symFileOff; |
| uint32_t numSyms; |
| uint32_t strFileOff; |
| uint32_t stringBytes; |
| } __attribute__((packed,aligned(1))); |
| |
| enum MachOLoadCommandCmd { |
| LCSegment32 = 0x00000001u, |
| LCSymTab = 0x00000002u, |
| LCSegment64 = 0x00000019u, |
| }; |
| |
| enum NListType { |
| NAbs = 0x2, |
| NSect = 0xe, |
| }; |
| |
| enum NListDescription { |
| ReferenceFlagDefined = 0x2, |
| }; |
| |
| struct __attribute__((packed)) NList64 { |
| uint32_t index; |
| uint8_t type; |
| uint8_t section; |
| uint16_t desc; |
| uint64_t value; |
| }; |
| |
| Writer::Slot<MachOHeader> writeHeader(Ref<Writer> writer) |
| { |
| ASSERT(!writer->position()); |
| Writer::Slot<MachOHeader> header = writer->createSlotHere<MachOHeader>(); |
| #if CPU(ARM64) |
| header->magic = 0xFEEDFACFu; |
| header->cpuType = 0x0000000C | 0x01000000; // ARM | 64-bit ABI |
| header->cpuSubtype = 0x00000000; // All |
| header->reserved = 0; |
| #elif CPU(X86_64) |
| header->magic = 0xFEEDFACFu; |
| header->cpuType = 7 | 0x01000000; // i386 | 64-bit ABI |
| header->cpuSubtype = 3; |
| header->reserved = 0; |
| #else |
| #error Unsupported target architecture. |
| #endif |
| header->fileType = 0x1; // MH_OBJECT |
| header->numCommands = 3; |
| header->sizeOfCommands = 0; |
| header->flags = 0; |
| return header; |
| } |
| |
| Writer::Slot<MachOSegmentCommand> writeSegmentCommand(Ref<Writer> writer, uintptr_t codeStart, uintptr_t codeSize) |
| { |
| auto cmd = writer->createSlotHere<MachOSegmentCommand>(); |
| cmd->cmd = LCSegment64; |
| cmd->vmAddr = codeStart; |
| cmd->vmSize = codeSize; |
| cmd->fileOff = 0; |
| cmd->fileSize = 0; |
| cmd->maxProt = 7; |
| cmd->initProt = 7; |
| cmd->flags = 0; |
| cmd->numSects = 1; |
| memset(cmd->segname, 0, 16); |
| cmd->cmdSize = sizeof(MachOSegmentCommand) + sizeof(MachOSection::Header) * cmd->numSects; |
| return cmd; |
| } |
| |
| Writer::Slot<MachOSymtabCommand> writeSymtabCommand(Ref<Writer> writer, const CString& name) |
| { |
| auto cmd = writer->createSlotHere<MachOSymtabCommand>(); |
| cmd->cmd = LCSymTab; |
| cmd->symFileOff = 0; |
| cmd->numSyms = 1; |
| cmd->strFileOff = 0; |
| cmd->stringBytes = name.length() + 1; |
| cmd->cmdSize = sizeof(MachOSymtabCommand); |
| return cmd; |
| } |
| |
| void writeNList(Ref<Writer> writer, Writer::Slot<MachOSymtabCommand> cmd, uintptr_t codeStart) |
| { |
| cmd->symFileOff = writer->position(); |
| auto slot = writer->createSlotHere<NList64>(); |
| slot->index = 1; |
| slot->type = NSect; |
| slot->section = 2; |
| slot->desc = 0; |
| slot->value = codeStart; |
| } |
| |
| void writeStringTable(Ref<Writer> writer, const CString& name, Writer::Slot<MachOSymtabCommand> cmd) |
| { |
| cmd->strFileOff = writer->position(); |
| writer->write<char>('\0'); // Index 0 |
| for (auto c : name.span()) |
| writer->write<char>(c); |
| writer->write<char>('\0'); |
| } |
| |
| Vector<std::unique_ptr<MachOSection>> m_sections { }; |
| }; |
| #endif // OS(DARWIN) |
| |
| #if OS(LINUX) |
| class ELF { |
| public: |
| explicit ELF() |
| { |
| m_sections.append(WTF::makeUnique<ELFSection>("", ELFSection::TypeNull, 0)); |
| m_sections.append(WTF::makeUnique<ELFStringTable>(".shstrtab")); |
| } |
| |
| void write(Ref<Writer> writer) |
| { |
| writeHeader(writer); |
| writeSectionTable(writer); |
| writeSections(writer); |
| } |
| |
| ELFSection* SectionAt(uint32_t index) { return m_sections[index].get(); } |
| |
| size_t addSection(std::unique_ptr<ELFSection> section) |
| { |
| section->setIndex(m_sections.size()); |
| m_sections.append(WTFMove(section)); |
| return m_sections.size() - 1; |
| } |
| |
| private: |
| struct ELFHeader { |
| uint8_t ident[16]; |
| uint16_t type; |
| uint16_t machine; |
| uint32_t version; |
| uintptr_t entry; |
| uintptr_t phtOffset; |
| uintptr_t shtOffset; |
| uint32_t flags; |
| uint16_t headerSize; |
| uint16_t phtEntrySize; |
| uint16_t phtEntryNum; |
| uint16_t shtEntrySize; |
| uint16_t shtEntryNum; |
| uint16_t shtStrTabIndex; |
| } __attribute__((packed,aligned(1))); |
| |
| void writeHeader(Ref<Writer> writer) |
| { |
| ASSERT(!writer->position()); |
| Writer::Slot<ELFHeader> header = writer->createSlotHere<ELFHeader>(); |
| #if CPU(ARM_THUMB2) |
| const uint8_t ident[16] = { |
| 0x7F, 'E', 'L', 'F', 1, 1, 1, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0 |
| }; |
| #elif CPU(X86_64) || CPU(ARM64) |
| const uint8_t ident[16] = { |
| 0x7F, 'E', 'L', 'F', 2, 1, 1, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0 |
| }; |
| #else |
| #error Unsupported target architecture. |
| #endif |
| memcpy(header->ident, ident, 16); |
| header->type = 1; |
| #if CPU(X86_64) |
| // Processor identification value for x64 is 62 as defined in |
| // System V ABI, AMD64 Supplement |
| // http://www.x86-64.org/documentation/abi.pdf |
| header->machine = 62; |
| #elif CPU(ARM_THUMB2) |
| // Set to EM_ARM, defined as 40, in "ARM ELF File Format" at |
| // infocenter.arm.com/help/topic/com.arm.doc.dui0101a/DUI0101A_Elf.pdf |
| header->machine = 40; |
| #elif CPU(ARM64) |
| // AARCH64 |
| header->machine = 0xB7; |
| #else |
| #error Unsupported target architecture. |
| #endif |
| header->version = 1; |
| header->entry = 0; |
| header->phtOffset = 0; |
| header->shtOffset = sizeof(ELFHeader); // Section table follows header. |
| header->flags = 0; |
| header->headerSize = sizeof(ELFHeader); |
| header->phtEntrySize = 0; |
| header->phtEntryNum = 0; |
| header->shtEntrySize = sizeof(ELFSection::Header); |
| header->shtEntryNum = m_sections.size(); |
| header->shtStrTabIndex = 1; |
| } |
| |
| void writeSectionTable(Ref<Writer> writer) |
| { |
| // Section headers table immediately follows file header. |
| ASSERT(writer->position() == sizeof(ELFHeader)); |
| |
| Writer::Slot<ELFSection::Header> headers = writer->createSlotsHere<ELFSection::Header>( |
| static_cast<uint32_t>(m_sections.size())); |
| |
| // String table for section table is the first section. |
| ELFStringTable* strtab = static_cast<ELFStringTable*>(SectionAt(1)); |
| ASSERT(strtab->m_type == ELFSection::TypeStrTab); |
| strtab->attachWriter(writer); |
| uint32_t index = 0; |
| for (auto& section : m_sections) { |
| section->populateHeader(headers.at(index), strtab); |
| index++; |
| } |
| strtab->detachWriter(); |
| } |
| |
| void writeSections(Ref<Writer> writer) |
| { |
| Writer::Slot<ELFSection::Header> headers = writer->slotAt<ELFSection::Header>(sizeof(ELFHeader)); |
| |
| uint32_t index = 0; |
| for (auto& section : m_sections) { |
| section->writeBody(headers.at(index), writer); |
| index++; |
| } |
| } |
| |
| Vector<std::unique_ptr<ELFSection>> m_sections; |
| }; |
| |
| class ELFSymbol { |
| public: |
| enum Type { |
| TypeNone = 0, |
| TypeObject = 1, |
| TypeFunction = 2, |
| TypeSection = 3, |
| TypeFile = 4, |
| TypeLoProc = 13, |
| TypeHiProc = 15 |
| }; |
| |
| enum Binding { |
| BindLocal = 0, |
| BindGlobal = 1, |
| BindWeak = 2, |
| BindLoProc = 13, |
| BindHiProc = 15 |
| }; |
| |
| ELFSymbol(const CString& name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) |
| : m_name(name) |
| , m_value(value) |
| , m_size(size) |
| , m_info((binding << 4) | type) |
| , m_other(0) |
| , m_section(section) |
| { |
| } |
| |
| Binding binding() const { return static_cast<Binding>(m_info >> 4); } |
| |
| #if CPU(ARM_THUMB2) |
| struct SerializedLayout { |
| SerializedLayout(uint32_t name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) |
| : m_name(name) |
| , m_value(value) |
| , m_size(size) |
| , m_info((binding << 4) | type) |
| , m_other(0) |
| , m_section(section) |
| { |
| } |
| |
| uint32_t m_name; |
| uintptr_t m_value; |
| uintptr_t m_size; |
| uint8_t m_info; |
| uint8_t m_other; |
| uint16_t m_section; |
| } __attribute__((packed,aligned(1))); |
| #elif CPU(X86_64) || CPU(ARM64) |
| struct SerializedLayout { |
| SerializedLayout(uint32_t name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) |
| : m_name(name) |
| , m_info((binding << 4) | type) |
| , m_other(0) |
| , m_section(section) |
| , m_value(value) |
| , m_size(size) |
| { |
| } |
| |
| uint32_t m_name; |
| uint8_t m_info; |
| uint8_t m_other; |
| uint16_t m_section; |
| uintptr_t m_value; |
| uintptr_t m_size; |
| } __attribute__((packed,aligned(1))); |
| #endif |
| |
| void write(Writer::Slot<SerializedLayout> slot, ELFStringTable* table) const |
| { |
| // Convert symbol names from strings to indexes in the string table. |
| slot->m_name = static_cast<uint32_t>(table->add(m_name)); |
| slot->m_value = m_value; |
| slot->m_size = m_size; |
| slot->m_info = m_info; |
| slot->m_other = m_other; |
| slot->m_section = m_section; |
| } |
| |
| private: |
| CString m_name; |
| uintptr_t m_value; |
| uintptr_t m_size; |
| uint8_t m_info; |
| uint8_t m_other; |
| uint16_t m_section; |
| }; |
| |
| class ELFSymbolTable : public ELFSection { |
| public: |
| ELFSymbolTable(const CString& name) |
| : ELFSection(name, TypeSymTab, sizeof(uintptr_t)) |
| { |
| } |
| |
| void writeBody(Writer::Slot<Header> header, Ref<Writer> writer) override |
| { |
| writer->align(header->alignment); |
| size_t totalSymbols = m_locals.size() + m_globals.size() + 1; |
| header->offset = writer->position(); |
| |
| Writer::Slot<ELFSymbol::SerializedLayout> symbols = writer->createSlotsHere<ELFSymbol::SerializedLayout>( |
| static_cast<uint32_t>(totalSymbols)); |
| |
| header->size = writer->position() - header->offset; |
| |
| // String table for this symbol table should follow it in the section table. |
| ELFStringTable* strtab = static_cast<ELFStringTable*>(writer->debugObject()->SectionAt(index() + 1)); |
| ASSERT(strtab->m_type == ELFSection::TypeStrTab); |
| strtab->attachWriter(writer); |
| symbols.at(0).set(ELFSymbol::SerializedLayout(0, 0, 0, ELFSymbol::BindLocal, ELFSymbol::TypeNone, 0)); |
| writeSymbolsList(&m_locals, symbols.at(1), strtab); |
| writeSymbolsList(&m_globals, symbols.at(static_cast<uint32_t>(m_locals.size() + 1)), strtab); |
| strtab->detachWriter(); |
| } |
| |
| void add(const ELFSymbol& symbol) |
| { |
| if (symbol.binding() == ELFSymbol::BindLocal) |
| m_locals.append(symbol); |
| else |
| m_globals.append(symbol); |
| } |
| |
| protected: |
| void populateHeader(Writer::Slot<Header> header) override |
| { |
| ELFSection::populateHeader(header); |
| // We are assuming that string table will follow symbol table. |
| header->link = index() + 1; |
| header->info = static_cast<uint32_t>(m_locals.size() + 1); |
| header->entrySize = sizeof(ELFSymbol::SerializedLayout); |
| } |
| |
| private: |
| void writeSymbolsList(const Vector<ELFSymbol>* src, |
| Writer::Slot<ELFSymbol::SerializedLayout> dst, |
| ELFStringTable* strtab) |
| { |
| int i = 0; |
| for (const ELFSymbol& symbol : *src) |
| symbol.write(dst.at(i++), strtab); |
| } |
| |
| Vector<ELFSymbol> m_locals; |
| Vector<ELFSymbol> m_globals; |
| }; |
| |
| static void createSymbolsTable(Ref<CodeDescription> desc, ELF* elf, size_t textSectionIndex) |
| { |
| auto symtab = WTF::makeUnique<ELFSymbolTable>(".symtab"); |
| auto strtab = WTF::makeUnique<ELFStringTable>(".strtab"); |
| |
| symtab->add(ELFSymbol("JSC Code", 0, 0, ELFSymbol::BindLocal, |
| ELFSymbol::TypeFile, ELFSection::IndexAbsolute)); |
| |
| symtab->add(ELFSymbol(desc->name(), 0, desc->codeSize(), |
| ELFSymbol::BindGlobal, ELFSymbol::TypeFunction, textSectionIndex)); |
| |
| // Symbol table should be followed by the linked string table. |
| elf->addSection(WTFMove(symtab)); |
| elf->addSection(WTFMove(strtab)); |
| } |
| #endif // OS(LINUX) |
| |
| class UnwindInfoSection : public DebugSection { |
| public: |
| explicit UnwindInfoSection(Ref<CodeDescription>); |
| bool writeBodyInternal(Ref<Writer>) override; |
| |
| int writeCIE(Ref<Writer>, uint32_t debugSectionStart); |
| Writer::Slot<int32_t> writeFDE(Ref<Writer>); |
| void writeFDEState(Ref<Writer>); |
| void WriteLength(Ref<Writer>, Writer::Slot<uint32_t>* lengthSlot, int initialPosition); |
| |
| private: |
| const Ref<CodeDescription> m_desc; |
| |
| // DWARF3 Specification, Table 7.23 |
| enum CFIInstructions { |
| AdvanceLoc = 0x40, |
| Offset = 0x80, |
| Restore = 0xC0, |
| Nop = 0x00, |
| SetLoc = 0x01, |
| AdvanceLoc1 = 0x02, |
| AdvanceLoc2 = 0x03, |
| AdvanceLoc4 = 0x04, |
| OffsetExtended = 0x05, |
| RestoreExtended = 0x06, |
| Undefined = 0x07, |
| SameValue = 0x08, |
| Register = 0x09, |
| RememberState = 0x0A, |
| RestoreState = 0x0B, |
| DefCFA = 0x0C, |
| DefCFARegister = 0x0D, |
| DefCFAOffset = 0x0E, |
| DefCFAExpression = 0x0F, |
| Expression = 0x10, |
| OffsetExtendedSF = 0x11, |
| DefCFASF = 0x12, |
| DefCFAOffsetSF = 0x13, |
| ValOffset = 0x14, |
| ValOffsetSF = 0x15, |
| ValExpression = 0x16 |
| }; |
| |
| // System V ABI, AMD64 Supplement, Version 0.99.5, Figure 3.36 |
| enum RegisterMapping { |
| // Only the relevant ones have been added to reduce clutter. |
| #if CPU(X86_64) |
| RegisterFP = 6, |
| RegisterLR = 16, |
| #elif CPU(ARM64) |
| RegisterFP = 29, |
| RegisterLR = 30, |
| #else |
| RegisterFP = 7, |
| RegisterLR = 14, |
| #endif |
| }; |
| |
| enum CFIConstants : uint32_t { |
| CIEID = UINT32_MAX, |
| CIEVersion = 4, |
| CodeAlignFactor = 1, |
| DataAlignFactor = 1, |
| ReturnAddressRegister = RegisterLR |
| }; |
| }; |
| |
| void UnwindInfoSection::WriteLength(Ref<Writer> writer, Writer::Slot<uint32_t>* lengthSlot, int initialPosition) |
| { |
| uint32_t align = (writer->position() - initialPosition) % sizeof(void*); |
| |
| if (align) { |
| for (uint32_t i = 0; i < (sizeof(void*) - align); i++) |
| writer->write<uint8_t>(Nop); |
| } |
| |
| ASSERT((writer->position() - initialPosition) % sizeof(void*) == 0); |
| lengthSlot->set(static_cast<uint32_t>(writer->position() - initialPosition)); |
| } |
| |
| UnwindInfoSection::UnwindInfoSection(Ref<CodeDescription> desc) |
| #if OS(LINUX) |
| : ELFSection(".debug_frame", TypeProgBits, 1) |
| #elif OS(DARWIN) |
| : MachOSection("__debug_frame", "__TEXT", sizeof(uintptr_t), 0, 0, MachOSection::Regular) |
| #else |
| #error "Unsupported platform" |
| #endif |
| , m_desc(desc) |
| { |
| } |
| |
| int UnwindInfoSection::writeCIE(Ref<Writer> writer, uint32_t) |
| { |
| auto ciePosition = static_cast<uint32_t>(writer->position()); |
| auto cieLengthSlot = writer->createSlotHere<uint32_t>(); |
| |
| writer->write<uint32_t>(CIEID); |
| writer->write<uint8_t>(CIEVersion); |
| writer->write<uint8_t>(0); // Null augmentation string. |
| writer->write<uint8_t>(sizeof(uintptr_t)); // Address size |
| writer->write<uint8_t>(0); // Segment size |
| writer->writeULEB128(CodeAlignFactor); |
| writer->writeSLEB128(DataAlignFactor); |
| writer->writeULEB128(ReturnAddressRegister); |
| |
| WriteLength(writer, &cieLengthSlot, ciePosition + sizeof(*cieLengthSlot)); |
| |
| return ciePosition; |
| } |
| |
| Writer::Slot<int32_t> UnwindInfoSection::writeFDE(Ref<Writer> writer) |
| { |
| int fdePosition = static_cast<uint32_t>(writer->position()); |
| auto fdeLengthSlot = writer->createSlotHere<uint32_t>(); |
| auto ciePointerSlot = writer->createSlotHere<int32_t>(); |
| |
| writer->write<uintptr_t>(reinterpret_cast<uintptr_t>(m_desc->codeStart())); |
| writer->write<uintptr_t>(reinterpret_cast<uintptr_t>(m_desc->codeSize())); |
| |
| writeFDEState(writer); |
| |
| WriteLength(writer, &fdeLengthSlot, fdePosition + sizeof(*fdeLengthSlot)); |
| return ciePointerSlot; |
| } |
| |
| // You can read an example unwind section from GCC: |
| // readelf --debug-dump=frames ./test |
| // Or: |
| // llvm-dwarfdump -a "/tmp/jit-8113659Thunk: CallTrampoline.o" |
| // Also, try adding `log enable lldb unwind` to your .lldbinit if you debug with lldb, |
| // or `set debug frame on` and `set debug jit on` for gdb. |
| // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf |
| // https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#dwarf-register-names |
| // https://dwarfstd.org/doc/DWARF5.pdf |
| /* |
| CFA means canonical frame address. These bytecodes define CFA in terms of other registers, |
| or other registers in terms of CFA. |
| |
| In the CIE: |
| DW_CFA_def_cfa: r31 (sp) ofs 0 |
| |
| 00000000004007a4 <main>: |
| 4007a4: d100c3ff sub sp, sp, #0x30 (decimal 48) |
| |
| DW_CFA_def_cfa_offset: 48 |
| |
| 4007a8: a9027bfd stp fp, lr, [sp, #32] |
| 4007ac: 910083fd add fp, sp, #0x20 |
| |
| DW_CFA_def_cfa: r29 (fp) ofs 16 |
| DW_CFA_offset: r30 (lr) at cfa-8 |
| DW_CFA_offset: r29 (fp) at cfa-16 |
| |
| <snip> |
| |
| DW_CFA_def_cfa: r31 (sp) ofs 48 |
| |
| 400840: a9427bfd ldp fp, lr, [sp, #32] |
| 400844: 9100c3ff add sp, sp, #0x30 |
| |
| DW_CFA_def_cfa_offset: 0 |
| DW_CFA_restore: r30 (lr) |
| DW_CFA_restore: r29 (fp) |
| 400848: d65f03c0 ret |
| |
| The generated table: |
| |
| 0x4007a4: CFA=WSP |
| 0x4007a8: CFA=WSP+48 |
| 0x4007b0: CFA=W29+16: W29=[CFA-16], W30=[CFA-8] |
| 0x400840: CFA=WSP+48: W29=[CFA-16], W30=[CFA-8] |
| 0x400848: CFA=WSP |
| */ |
| |
| void UnwindInfoSection::writeFDEState(Ref<Writer> writer) |
| { |
| // The first state, just after the control has been transferred to the the |
| // function. |
| // Since we just want simple unwinding to work, we will just use this blanket rule to |
| // force the unwidner to check fp. It won't be accurate, but it should be good enough for |
| // basic debugging. |
| // Consider when we are after these instructions: |
| // stp fp, lr |
| // mov fp, sp |
| // Then: |
| // CFA = fp (current) + 8 |
| // fp (previous value) = *(CFA - 16); lr / Return Address (saved) = *(CFA - 8); |
| // Start with a bogus rule. LLDB is off by one some times, and this seemed to fix it for some reason. |
| writer->write<uint8_t>(DefCFASF); |
| writer->writeULEB128(RegisterFP); |
| writer->writeSLEB128(0); |
| writer->write<uint8_t>(AdvanceLoc1); |
| writer->write<uint8_t>(is32Bit() ? 2 : 4); |
| writer->write<uint8_t>(DefCFASF); |
| writer->writeULEB128(RegisterFP); |
| writer->writeSLEB128(static_cast<int32_t>(sizeof(CallerFrameAndPC))); |
| writer->write<uint8_t>(OffsetExtendedSF); |
| writer->writeULEB128(RegisterLR); |
| writer->writeSLEB128(-static_cast<int32_t>(sizeof(uintptr_t))); |
| writer->write<uint8_t>(OffsetExtendedSF); |
| writer->writeULEB128(RegisterFP); |
| writer->writeSLEB128(-2 * static_cast<int32_t>(sizeof(uintptr_t))); |
| } |
| |
| bool UnwindInfoSection::writeBodyInternal(Ref<Writer> writer) |
| { |
| uint32_t debugSectionStart = writer->position(); |
| // This is a throw-away; The CIE must come first for LLDB to be happy, but |
| // the offset can't be 0 according to the spec / gdb. |
| writeCIE(writer, debugSectionStart); |
| auto ciePosition = writeCIE(writer, debugSectionStart); |
| auto ciePointer = writeFDE(writer); |
| ciePointer.set(ciePosition - debugSectionStart); |
| return true; |
| } |
| |
| static JITCodeEntry* createELFObject(Ref<CodeDescription> desc) |
| { |
| constexpr int codeAlignment = 4; |
| #if OS(DARWIN) |
| MachO machO; |
| auto writer = Writer::create(&machO); |
| |
| if constexpr (isARM64()) |
| machO.addSection(WTF::makeUnique<UnwindInfoSection>(desc)); |
| machO.addSection(WTF::makeUnique<MachOTextSection>(codeAlignment, desc->codeStart(), desc->codeSize())); |
| |
| machO.write(writer, desc->name(), reinterpret_cast<uintptr_t>(desc->codeStart()), desc->codeSize()); |
| #else |
| ELF elf; |
| auto writer = Writer::create(&elf); |
| |
| size_t textSectionIndex = elf.addSection(WTF::makeUnique<FullHeaderELFSection>( |
| ".text", ELFSection::TypeNoBits, codeAlignment, desc->codeStart(), 0, |
| desc->codeSize(), ELFSection::FlagAlloc | ELFSection::FlagExec)); |
| |
| createSymbolsTable(desc, &elf, textSectionIndex); |
| if constexpr (isARM64() || is32Bit()) |
| elf.addSection(WTF::makeUnique<UnwindInfoSection>(desc)); |
| |
| elf.write(writer); |
| #endif |
| |
| return createCodeEntry(writer->buffer(), writer->position()); |
| } |
| |
| static std::optional<std::pair<GdbJITCodeMap::iterator, GdbJITCodeMap::iterator>> |
| getOverlappingRegions(GdbJITCodeMap& map, const std::span<const uint8_t> region) |
| { |
| ASSERT(region.data() < region.data() + region.size()); |
| |
| if (map.empty()) |
| return std::nullopt; |
| |
| // Find the first overlapping entry. |
| |
| // If successful, points to the first element not less than `region`. The |
| // returned iterator has the key in `first` and the value in `second`. |
| auto it = map.lower_bound(region); |
| auto startIt = it; |
| |
| if (it == map.end()) { |
| startIt = map.begin(); |
| // Find the first overlapping entry. |
| for (; startIt != map.end(); ++startIt) { |
| if (startIt->first.data() + startIt->first.size() > region.data()) |
| break; |
| } |
| } else if (it != map.begin()) { |
| for (--it; it != map.begin(); --it) { |
| if (it->first.data() + it->first.size() <= region.data()) |
| break; |
| startIt = it; |
| } |
| if (it == map.begin() && it->first.data() + it->first.size() > region.data()) |
| startIt = it; |
| } |
| |
| if (startIt == map.end()) |
| return std::nullopt; |
| |
| // Find the first non-overlapping entry after `region`. |
| |
| const auto endIt = map.lower_bound({ region.data() + region.size(), 0 }); |
| |
| // Return a range containing intersecting regions. |
| |
| if (std::distance(startIt, endIt) < 1) |
| return std::nullopt; // No overlapping entries. |
| |
| return { { startIt, endIt } }; |
| } |
| |
| static void removeJITCodeEntries(GdbJITCodeMap& map, const std::span<const uint8_t> region) |
| { |
| if (auto overlap = getOverlappingRegions(map, region)) { |
| auto startIt = overlap->first; |
| auto endIt = overlap->second; |
| for (auto it = startIt; it != endIt; ++it) |
| unregisterCodeEntry(it->second); |
| |
| map.erase(startIt, endIt); |
| } |
| } |
| |
| // Insert the entry into the map and register it with GDB. |
| static void addJITCodeEntry(GdbJITCodeMap& map, std::span<const uint8_t> region, |
| JITCodeEntry* entry, bool shouldDump, const CString& nameHint) |
| { |
| static int fileNum = 0; |
| if (shouldDump) { |
| StringPrintStream filename; |
| if (auto* optionalDirectory = Options::jitDumpDirectory()) |
| filename.print(optionalDirectory); |
| else |
| filename.print("/tmp"); |
| filename.print("/jit-", getCurrentProcessID(), fileNum++, nameHint, ".o"); |
| auto fd = open(filename.toCString().data(), O_CREAT | O_TRUNC | O_RDWR, 0666); |
| RELEASE_ASSERT(fd != -1); |
| auto file = fdopen(fd, "wb"); |
| RELEASE_ASSERT(file); |
| |
| fwrite(entry->symfileAddr, entry->symfileSize, 1, file); |
| fflush(file); |
| dataLogLnIf(GdbJITInternal::verbose, "GDBInfo dumped: ", nameHint, " ", RawPointer(region.data()), "-", RawPointer(region.data() + region.size()), " ", region.size(), " ", filename.toCString().data()); |
| } |
| |
| auto result = map.emplace(region, entry); |
| ASSERT_UNUSED(result, result.second); // Insertion happened. |
| |
| registerCodeEntry(entry); |
| } |
| |
| void GdbJIT::log(const CString& name, MacroAssemblerCodeRef<LinkBufferPtrTag> code) |
| { |
| if (!Options::useGdbJITInfo()) |
| return; |
| GdbJIT& logger = singleton(); |
| size_t size = code.size(); |
| auto* executableAddress = code.code().untaggedPtr<const uint8_t*>(); |
| if (!size) { |
| dataLogLnIf(GdbJITInternal::verbose, "0 size record ", name, " ", RawPointer(executableAddress)); |
| return; |
| } |
| |
| Locker locker { logger.m_lock }; |
| |
| auto region = unsafeMakeSpan(executableAddress, size); |
| removeJITCodeEntries(logger.m_map, region); |
| auto* entry = createELFObject(CodeDescription::create(name, region)); |
| bool shouldDump = false; |
| addJITCodeEntry(logger.m_map, region, entry, shouldDump, name); |
| } |
| |
| } // namespace JSC |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_END |
| |
| #else |
| |
| namespace JSC { |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(GdbJIT); |
| |
| GdbJIT& GdbJIT::singleton() |
| { |
| static LazyNeverDestroyed<GdbJIT> logger; |
| static std::once_flag onceKey; |
| std::call_once(onceKey, [] { |
| logger.construct(); |
| }); |
| return logger.get(); |
| } |
| |
| void GdbJIT::log(const CString&, MacroAssemblerCodeRef<LinkBufferPtrTag>) { } |
| |
| } // namespace JSC |
| |
| #endif // OS(DARWIN) || OS(LINUX) |
| #endif // ENABLE(ASSEMBLER) |