[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/jit/GdbJIT.cpp [Back]  [Original]

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

Web Proxy Viewer  |  New URL  |  Original Page