FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

/* * 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 #if ENABLE(ASSEMBLER) #if OS(DARWIN) || OS(LINUX) #include "CallFrame.h" #include "CallFrameInlines.h" #include "Options.h" #include "ProfilerSupport.h" #include #include #include #include #include #include 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 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 static inline void writeUnalignedValue(uint8_t* p, V value) { memcpy(p, &value, sizeof(V)); } class Writer : public RefCountedAndCanMakeWeakPtr { public: static Ref NODELETE create(DebugObject* obj) { return adoptRef(*new Writer(obj)); } uintptr_t NODELETE position() const { return m_position; } template class Slot { public: Slot(WeakPtr writer, uintptr_t offset) : m_writer(writer) , m_offset(offset) { } T* operator->() { return m_writer->template rawSlotAt(m_offset); } T operator*() { return *m_writer->template rawSlotAt(m_offset); } void set(const T& value) { writeUnalignedValue(m_writer->template addressAt(m_offset), value); } Slot at(int i) { return Slot(m_writer, m_offset + sizeof(T) * i); } private: WeakPtr m_writer; uintptr_t m_offset; }; template void write(const T& val) { ensure(m_position + sizeof(T)); writeUnalignedValue(addressAt(m_position), val); m_position += sizeof(T); } template Slot slotAt(uintptr_t offset) LIFETIME_BOUND { ensure(offset + sizeof(T)); return Slot(*this, offset); } template Slot createSlotHere() LIFETIME_BOUND { return createSlotsHere(1); } template Slot createSlotsHere(uint32_t count) LIFETIME_BOUND { uintptr_t slotPosition = m_position; m_position += sizeof(T) * count; ensure(m_position); return slotAt(slotPosition); } void ensure(uintptr_t pos) { if (m_buffer.size() < pos) m_buffer.grow(pos); } DebugObject* NODELETE debugObject() { return m_debugObject; } uint8_t* NODELETE 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(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(byte); } } void writeString(const CString& str) { for (auto c : str.span()) write(c); write('\0'); } private: template friend class Slot; template uint8_t* NODELETE addressAt(uintptr_t offset) LIFETIME_BOUND { ASSERT(offset < m_buffer.size() && offset + sizeof(T) 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 class DebugSectionBase { WTF_DEPRECATED_MAKE_FAST_ALLOCATED(DebugSectionBase); public: virtual ~DebugSectionBase() = default; virtual void writeBody(Writer::Slot header, Ref writer) { uint64_t start = writer->position(); if (writeBodyInternal(writer)) { header->offset = static_cast(start); uint64_t end = writer->position(); header->size = std::max(end - start, static_cast(header->size)); } } virtual bool writeBodyInternal(Ref) { 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 { 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->addr = reinterpret_cast(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* NODELETE addr() const { return m_addr; } size_t NODELETE 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 { 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, ELFStringTable* strtab); void writeBody(Writer::Slot header, Ref 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) 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->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(codeAddr)) , m_codeSize(codeSize) { } bool writeBodyInternal(Ref writer) override { for (auto* ptr = m_codeAddr; ptr < m_codeAddr + m_codeSize / sizeof(uint64_t); ++ptr) writer->write(*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) 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 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, Ref) 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 m_writer; uintptr_t m_offset; uintptr_t m_size; }; void ELFSection::populateHeader(Writer::Slot header, ELFStringTable* strtab) { header->name = static_cast(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 section) { m_sections.append(WTF::move(section)); return m_sections.size() - 1; } void write(Ref w, const CString& name, uintptr_t codeStart, uintptr_t) { Writer::Slot header = writeHeader(w); uintptr_t loadCommandStart = w->position(); Vector segmentCommands; Vector sectionHeadersForSegments; for (auto& section : m_sections) { segmentCommands.append(writeSegmentCommand(w, reinterpret_cast(section->addr()), section->size())); sectionHeadersForSegments.append(w->createSlotHere()); } auto symtabCmd = writeSymtabCommand(w, name); header->sizeOfCommands = static_cast(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; uint32_t reserved; } __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 writeHeader(Ref writer) { ASSERT(!writer->position()); Writer::Slot header = writer->createSlotHere(); #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 writeSegmentCommand(Ref writer, uintptr_t codeStart, uintptr_t codeSize) { auto cmd = writer->createSlotHere(); 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 writeSymtabCommand(Ref writer, const CString& name) { auto cmd = writer->createSlotHere(); 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::Slot cmd, uintptr_t codeStart) { cmd->symFileOff = writer->position(); auto slot = writer->createSlotHere(); slot->index = 1; slot->type = NSect; slot->section = 2; slot->desc = 0; slot->value = codeStart; } void writeStringTable(Ref writer, const CString& name, Writer::Slot cmd) { cmd->strFileOff = writer->position(); writer->write('\0'); // Index 0 for (auto c : name.span()) writer->write(c); writer->write('\0'); } Vector m_sections { }; }; #endif // OS(DARWIN) #if OS(LINUX) class ELF { public: explicit ELF() { m_sections.append(WTF::makeUnique("", ELFSection::TypeNull, 0)); m_sections.append(WTF::makeUnique(".shstrtab")); } void write(Ref writer) { writeHeader(writer); writeSectionTable(writer); writeSections(writer); } ELFSection* SectionAt(uint32_t index) { return m_sections[index].get(); } size_t addSection(std::unique_ptr section) { section->setIndex(m_sections.size()); m_sections.append(WTF::move(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) { ASSERT(!writer->position()); Writer::Slot header = writer->createSlotHere(); #if CPU(X86_64) || CPU(ARM64) || CPU(RISCV64) 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(ARM64) // AARCH64 header->machine = 0xB7; #elif CPU(RISCV64) // RISC-V 64 header->machine = 0xF3; #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) { // Section headers table immediately follows file header. ASSERT(writer->position() == sizeof(ELFHeader)); Writer::Slot headers = writer->createSlotsHere( static_cast(m_sections.size())); // String table for section table is the first section. ELFStringTable* strtab = static_cast(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::Slot headers = writer->slotAt(sizeof(ELFHeader)); uint32_t index = 0; for (auto& section : m_sections) { section->writeBody(headers.at(index), writer); index++; } } Vector 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); } #if CPU(X86_64) || CPU(ARM64) || CPU(RISCV64) 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 m_name = static_cast(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, Ref writer) override { writer->align(header->alignment); size_t totalSymbols = m_locals.size() + m_globals.size() + 1; header->offset = writer->position(); Writer::Slot symbols = writer->createSlotsHere( static_cast(totalSymbols)); header->size = writer->position() - header->offset; // String table for this symbol table should follow it in the section table. ELFStringTable* strtab = static_cast(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(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) override { ELFSection::populateHeader(header); // We are assuming that string table will follow symbol table. header->link = index() + 1; header->info = static_cast(m_locals.size() + 1); header->entrySize = sizeof(ELFSymbol::SerializedLayout); } private: void writeSymbolsList(const Vector* src, Writer::Slot dst, ELFStringTable* strtab) { int i = 0; for (const ELFSymbol& symbol : *src) symbol.write(dst.at(i++), strtab); } Vector m_locals; Vector m_globals; }; static void createSymbolsTable(Ref desc, ELF* elf, size_t textSectionIndex) { auto symtab = WTF::makeUnique(".symtab"); auto strtab = WTF::makeUnique(".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(WTF::move(symtab)); elf->addSection(WTF::move(strtab)); } #endif // OS(LINUX) class UnwindInfoSection : public DebugSection { public: explicit UnwindInfoSection(Ref); bool writeBodyInternal(Ref) override; int writeCIE(Ref, uint32_t debugSectionStart); Writer::Slot writeFDE(Ref); void writeFDEState(Ref); void WriteLength(Ref, Writer::Slot* lengthSlot, int initialPosition); private: const Ref 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::Slot* 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(Nop); } ASSERT((writer->position() - initialPosition) % sizeof(void*) == 0); lengthSlot->set(static_cast(writer->position() - initialPosition)); } UnwindInfoSection::UnwindInfoSection(Ref 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, uint32_t) { auto ciePosition = static_cast(writer->position()); auto cieLengthSlot = writer->createSlotHere(); writer->write(CIEID); writer->write(CIEVersion); writer->write(0); // Null augmentation string. writer->write(sizeof(uintptr_t)); // Address size writer->write(0); // Segment size writer->writeULEB128(CodeAlignFactor); writer->writeSLEB128(DataAlignFactor); writer->writeULEB128(ReturnAddressRegister); WriteLength(writer, &cieLengthSlot, ciePosition + sizeof(*cieLengthSlot)); return ciePosition; } Writer::Slot UnwindInfoSection::writeFDE(Ref writer) { int fdePosition = static_cast(writer->position()); auto fdeLengthSlot = writer->createSlotHere(); auto ciePointerSlot = writer->createSlotHere(); writer->write(reinterpret_cast(m_desc->codeStart())); writer->write(reinterpret_cast(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 : 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 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) { // 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(DefCFASF); writer->writeULEB128(RegisterFP); writer->writeSLEB128(0); writer->write(AdvanceLoc1); writer->write(4); writer->write(DefCFASF); writer->writeULEB128(RegisterFP); writer->writeSLEB128(static_cast(sizeof(CallerFrameAndPC))); writer->write(OffsetExtendedSF); writer->writeULEB128(RegisterLR); writer->writeSLEB128(-static_cast(sizeof(uintptr_t))); writer->write(OffsetExtendedSF); writer->writeULEB128(RegisterFP); writer->writeSLEB128(-2 * static_cast(sizeof(uintptr_t))); } bool UnwindInfoSection::writeBodyInternal(Ref 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 desc) { constexpr int codeAlignment = 4; #if OS(DARWIN) MachO machO; auto writer = Writer::create(&machO); if constexpr (isARM64()) machO.addSection(WTF::makeUnique(desc)); machO.addSection(WTF::makeUnique(codeAlignment, desc->codeStart(), desc->codeSize())); machO.write(writer, desc->name(), reinterpret_cast(desc->codeStart()), desc->codeSize()); #else ELF elf; auto writer = Writer::create(&elf); size_t textSectionIndex = elf.addSection(WTF::makeUnique( ".text", ELFSection::TypeNoBits, codeAlignment, desc->codeStart(), 0, desc->codeSize(), ELFSection::FlagAlloc | ELFSection::FlagExec)); createSymbolsTable(desc, &elf, textSectionIndex); if constexpr (isARM64()) elf.addSection(WTF::makeUnique(desc)); elf.write(writer); #endif return createCodeEntry(writer->buffer(), writer->position()); } static std::optional getOverlappingRegions(GdbJITCodeMap& map, const std::span region) { 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 (std::to_address(startIt->first.end()) > region.data()) break; } } else if (it != map.begin()) { for (--it; it != map.begin(); --it) { if (std::to_address(it->first.end()) first.end()) > 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({ std::to_address(region.end()), 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 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 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(std::to_address(region.end())), " ", 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 code) { if (!Options::useGdbJITInfo()) return; GdbJIT& logger = singleton(); size_t size = code.size(); auto* executableAddress = code.code().untaggedPtr(); 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 logger; static std::once_flag onceKey; std::call_once(onceKey, [] { logger.construct(); }); return logger.get(); } void GdbJIT::log(const CString&, MacroAssemblerCodeRef) { } } // namespace JSC #endif // OS(DARWIN) || OS(LINUX) #endif // ENABLE(ASSEMBLER)

Back | FazBrowse Home | New Git URL