// Copyright 2010 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/gdb-jit.h" #include #include #include "src/api-inl.h" #include "src/base/bits.h" #include "src/base/platform/platform.h" #include "src/bootstrapper.h" #include "src/frames-inl.h" #include "src/frames.h" #include "src/global-handles.h" #include "src/messages.h" #include "src/objects.h" #include "src/ostreams.h" #include "src/snapshot/natives.h" #include "src/splay-tree-inl.h" #include "src/zone/zone-chunk-list.h" namespace v8 { namespace internal { namespace GDBJITInterface { #ifdef ENABLE_GDB_JIT_INTERFACE #ifdef __APPLE__ #define __MACH_O class MachO; class MachOSection; typedef MachO DebugObject; typedef MachOSection DebugSection; #else #define __ELF class ELF; class ELFSection; typedef ELF DebugObject; typedef ELFSection DebugSection; #endif class Writer { public: explicit Writer(DebugObject* debug_object) : debug_object_(debug_object), position_(0), capacity_(1024), buffer_(reinterpret_cast(malloc(capacity_))) { } ~Writer() { free(buffer_); } uintptr_t position() const { return position_; } template class Slot { public: Slot(Writer* w, uintptr_t offset) : w_(w), offset_(offset) { } T* operator-> () { return w_->RawSlotAt(offset_); } void set(const T& value) { *w_->RawSlotAt(offset_) = value; } Slot at(int i) { return Slot(w_, offset_ + sizeof(T) * i); } private: Writer* w_; uintptr_t offset_; }; template void Write(const T& val) { Ensure(position_ + sizeof(T)); *RawSlotAt(position_) = val; position_ += sizeof(T); } template Slot SlotAt(uintptr_t offset) { Ensure(offset + sizeof(T)); return Slot(this, offset); } template Slot CreateSlotHere() { return CreateSlotsHere(1); } template Slot CreateSlotsHere(uint32_t count) { uintptr_t slot_position = position_; position_ += sizeof(T) * count; Ensure(position_); return SlotAt(slot_position); } void Ensure(uintptr_t pos) { if (capacity_ < pos) { while (capacity_ < pos) capacity_ *= 2; buffer_ = reinterpret_cast(realloc(buffer_, capacity_)); } } DebugObject* debug_object() { return debug_object_; } byte* buffer() { return buffer_; } void Align(uintptr_t align) { uintptr_t delta = position_ % align; if (delta == 0) return; uintptr_t padding = align - delta; Ensure(position_ += padding); DCHECK_EQ(position_ % align, 0); } void WriteULEB128(uintptr_t value) { do { uint8_t byte = value & 0x7F; value >>= 7; if (value != 0) byte |= 0x80; Write(byte); } while (value != 0); } void WriteSLEB128(intptr_t value) { bool more = true; while (more) { int8_t byte = value & 0x7F; bool byte_sign = byte & 0x40; value >>= 7; if ((value == 0 && !byte_sign) || (value == -1 && byte_sign)) { more = false; } else { byte |= 0x80; } Write(byte); } } void WriteString(const char* str) { do { Write(*str); } while (*str++); } private: template friend class Slot; template T* RawSlotAt(uintptr_t offset) { DCHECK(offset < capacity_ && offset + sizeof(T) <= capacity_); return reinterpret_cast(&buffer_[offset]); } DebugObject* debug_object_; uintptr_t position_; uintptr_t capacity_; byte* buffer_; }; class ELFStringTable; template class DebugSectionBase : public ZoneObject { public: virtual ~DebugSectionBase() = default; virtual void WriteBody(Writer::Slot header, Writer* writer) { uintptr_t start = writer->position(); if (WriteBodyInternal(writer)) { uintptr_t end = writer->position(); header->offset = static_cast(start); #if defined(__MACH_O) header->addr = 0; #endif header->size = end - start; } } virtual bool WriteBodyInternal(Writer* writer) { return false; } typedef THeader Header; }; struct MachOSectionHeader { char sectname[16]; char segname[16]; #if V8_TARGET_ARCH_IA32 uint32_t addr; uint32_t size; #else uint64_t addr; uint64_t size; #endif uint32_t offset; uint32_t align; uint32_t reloff; uint32_t nreloc; uint32_t flags; uint32_t reserved1; uint32_t reserved2; }; class MachOSection : public DebugSectionBase { public: enum Type { S_REGULAR = 0x0u, S_ATTR_COALESCED = 0xBu, S_ATTR_SOME_INSTRUCTIONS = 0x400u, S_ATTR_DEBUG = 0x02000000u, S_ATTR_PURE_INSTRUCTIONS = 0x80000000u }; MachOSection(const char* name, const char* segment, uint32_t align, uint32_t flags) : name_(name), segment_(segment), align_(align), flags_(flags) { if (align_ != 0) { DCHECK(base::bits::IsPowerOfTwo(align)); align_ = WhichPowerOf2(align_); } } ~MachOSection() override = default; virtual void PopulateHeader(Writer::Slot
header) { header->addr = 0; header->size = 0; header->offset = 0; header->align = align_; header->reloff = 0; header->nreloc = 0; header->flags = flags_; header->reserved1 = 0; header->reserved2 = 0; memset(header->sectname, 0, sizeof(header->sectname)); memset(header->segname, 0, sizeof(header->segname)); DCHECK(strlen(name_) < sizeof(header->sectname)); DCHECK(strlen(segment_) < sizeof(header->segname)); strncpy(header->sectname, name_, sizeof(header->sectname)); strncpy(header->segname, segment_, sizeof(header->segname)); } private: const char* name_; const char* segment_; uint32_t align_; uint32_t flags_; }; struct ELFSectionHeader { uint32_t name; uint32_t type; uintptr_t flags; uintptr_t address; uintptr_t offset; uintptr_t size; uint32_t link; uint32_t info; uintptr_t alignment; uintptr_t entry_size; }; #if defined(__ELF) class ELFSection : public DebugSectionBase { public: enum Type { TYPE_NULL = 0, TYPE_PROGBITS = 1, TYPE_SYMTAB = 2, TYPE_STRTAB = 3, TYPE_RELA = 4, TYPE_HASH = 5, TYPE_DYNAMIC = 6, TYPE_NOTE = 7, TYPE_NOBITS = 8, TYPE_REL = 9, TYPE_SHLIB = 10, TYPE_DYNSYM = 11, TYPE_LOPROC = 0x70000000, TYPE_X86_64_UNWIND = 0x70000001, TYPE_HIPROC = 0x7FFFFFFF, TYPE_LOUSER = 0x80000000, TYPE_HIUSER = 0xFFFFFFFF }; enum Flags { FLAG_WRITE = 1, FLAG_ALLOC = 2, FLAG_EXEC = 4 }; enum SpecialIndexes { INDEX_ABSOLUTE = 0xFFF1 }; ELFSection(const char* name, Type type, uintptr_t align) : name_(name), type_(type), align_(align) { } ~ELFSection() override = default; void PopulateHeader(Writer::Slot
header, ELFStringTable* strtab); void WriteBody(Writer::Slot
header, Writer* w) override { uintptr_t start = w->position(); if (WriteBodyInternal(w)) { uintptr_t end = w->position(); header->offset = start; header->size = end - start; } } bool WriteBodyInternal(Writer* w) override { return false; } uint16_t index() const { return index_; } void set_index(uint16_t index) { index_ = index; } 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->entry_size = 0; } private: const char* name_; Type type_; uintptr_t align_; uint16_t index_; }; #endif // defined(__ELF) #if defined(__MACH_O) class MachOTextSection : public MachOSection { public: MachOTextSection(uint32_t align, uintptr_t addr, uintptr_t size) : MachOSection("__text", "__TEXT", align, MachOSection::S_REGULAR | MachOSection::S_ATTR_SOME_INSTRUCTIONS | MachOSection::S_ATTR_PURE_INSTRUCTIONS), addr_(addr), size_(size) {} protected: virtual void PopulateHeader(Writer::Slot
header) { MachOSection::PopulateHeader(header); header->addr = addr_; header->size = size_; } private: uintptr_t addr_; uintptr_t size_; }; #endif // defined(__MACH_O) #if defined(__ELF) class FullHeaderELFSection : public ELFSection { public: FullHeaderELFSection(const char* name, Type type, uintptr_t align, uintptr_t addr, uintptr_t offset, uintptr_t size, uintptr_t flags) : ELFSection(name, type, align), addr_(addr), offset_(offset), size_(size), flags_(flags) { } protected: void PopulateHeader(Writer::Slot
header) override { ELFSection::PopulateHeader(header); header->address = addr_; header->offset = offset_; header->size = size_; header->flags = flags_; } private: uintptr_t addr_; uintptr_t offset_; uintptr_t size_; uintptr_t flags_; }; class ELFStringTable : public ELFSection { public: explicit ELFStringTable(const char* name) : ELFSection(name, TYPE_STRTAB, 1), writer_(nullptr), offset_(0), size_(0) {} uintptr_t Add(const char* str) { if (*str == '\0') return 0; uintptr_t offset = size_; WriteString(str); return offset; } void AttachWriter(Writer* w) { writer_ = w; offset_ = writer_->position(); // First entry in the string table should be an empty string. WriteString(""); } void DetachWriter() { writer_ = nullptr; } void WriteBody(Writer::Slot
header, Writer* w) override { DCHECK_NULL(writer_); header->offset = offset_; header->size = size_; } private: void WriteString(const char* str) { uintptr_t written = 0; do { writer_->Write(*str); written++; } while (*str++); size_ += written; } Writer* writer_; uintptr_t offset_; uintptr_t size_; }; void ELFSection::PopulateHeader(Writer::Slot header, ELFStringTable* strtab) { header->name = static_cast(strtab->Add(name_)); header->type = type_; header->alignment = align_; PopulateHeader(header); } #endif // defined(__ELF) #if defined(__MACH_O) class MachO { public: explicit MachO(Zone* zone) : sections_(zone) {} size_t AddSection(MachOSection* section) { sections_.push_back(section); return sections_.size() - 1; } void Write(Writer* w, uintptr_t code_start, uintptr_t code_size) { Writer::Slot header = WriteHeader(w); uintptr_t load_command_start = w->position(); Writer::Slot cmd = WriteSegmentCommand(w, code_start, code_size); WriteSections(w, cmd, header, load_command_start); } private: struct MachOHeader { uint32_t magic; uint32_t cputype; uint32_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; #if V8_TARGET_ARCH_X64 uint32_t reserved; #endif }; struct MachOSegmentCommand { uint32_t cmd; uint32_t cmdsize; char segname[16]; #if V8_TARGET_ARCH_IA32 uint32_t vmaddr; uint32_t vmsize; uint32_t fileoff; uint32_t filesize; #else uint64_t vmaddr; uint64_t vmsize; uint64_t fileoff; uint64_t filesize; #endif uint32_t maxprot; uint32_t initprot; uint32_t nsects; uint32_t flags; }; enum MachOLoadCommandCmd { LC_SEGMENT_32 = 0x00000001u, LC_SEGMENT_64 = 0x00000019u }; Writer::Slot WriteHeader(Writer* w) { DCHECK_EQ(w->position(), 0); Writer::Slot header = w->CreateSlotHere(); #if V8_TARGET_ARCH_IA32 header->magic = 0xFEEDFACEu; header->cputype = 7; // i386 header->cpusubtype = 3; // CPU_SUBTYPE_I386_ALL #elif V8_TARGET_ARCH_X64 header->magic = 0xFEEDFACFu; header->cputype = 7 | 0x01000000; // i386 | 64-bit ABI header->cpusubtype = 3; // CPU_SUBTYPE_I386_ALL header->reserved = 0; #else #error Unsupported target architecture. #endif header->filetype = 0x1; // MH_OBJECT header->ncmds = 1; header->sizeofcmds = 0; header->flags = 0; return header; } Writer::Slot WriteSegmentCommand(Writer* w, uintptr_t code_start, uintptr_t code_size) { Writer::Slot cmd = w->CreateSlotHere(); #if V8_TARGET_ARCH_IA32 cmd->cmd = LC_SEGMENT_32; #else cmd->cmd = LC_SEGMENT_64; #endif cmd->vmaddr = code_start; cmd->vmsize = code_size; cmd->fileoff = 0; cmd->filesize = 0; cmd->maxprot = 7; cmd->initprot = 7; cmd->flags = 0; cmd->nsects = static_cast(sections_.size()); memset(cmd->segname, 0, 16); cmd->cmdsize = sizeof(MachOSegmentCommand) + sizeof(MachOSection::Header) * cmd->nsects; return cmd; } void WriteSections(Writer* w, Writer::Slot cmd, Writer::Slot header, uintptr_t load_command_start) { Writer::Slot headers = w->CreateSlotsHere( static_cast(sections_.size())); cmd->fileoff = w->position(); header->sizeofcmds = static_cast(w->position() - load_command_start); uint32_t index = 0; for (MachOSection* section : sections_) { section->PopulateHeader(headers.at(index)); section->WriteBody(headers.at(index), w); index++; } cmd->filesize = w->position() - (uintptr_t)cmd->fileoff; } ZoneChunkList sections_; }; #endif // defined(__MACH_O) #if defined(__ELF) class ELF { public: explicit ELF(Zone* zone) : sections_(zone) { sections_.push_back(new (zone) ELFSection("", ELFSection::TYPE_NULL, 0)); sections_.push_back(new (zone) ELFStringTable(".shstrtab")); } void Write(Writer* w) { WriteHeader(w); WriteSectionTable(w); WriteSections(w); } ELFSection* SectionAt(uint32_t index) { return *sections_.Find(index); } size_t AddSection(ELFSection* section) { sections_.push_back(section); section->set_index(sections_.size() - 1); return sections_.size() - 1; } private: struct ELFHeader { uint8_t ident[16]; uint16_t type; uint16_t machine; uint32_t version; uintptr_t entry; uintptr_t pht_offset; uintptr_t sht_offset; uint32_t flags; uint16_t header_size; uint16_t pht_entry_size; uint16_t pht_entry_num; uint16_t sht_entry_size; uint16_t sht_entry_num; uint16_t sht_strtab_index; }; void WriteHeader(Writer* w) { DCHECK_EQ(w->position(), 0); Writer::Slot header = w->CreateSlotHere(); #if (V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_ARM || \ (V8_TARGET_ARCH_X64 && V8_TARGET_ARCH_32_BIT)) const uint8_t ident[16] = {0x7F, 'E', 'L', 'F', 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #elif(V8_TARGET_ARCH_X64 && V8_TARGET_ARCH_64_BIT) || \ (V8_TARGET_ARCH_PPC64 && V8_TARGET_LITTLE_ENDIAN) const uint8_t ident[16] = {0x7F, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #elif V8_TARGET_ARCH_PPC64 && V8_TARGET_BIG_ENDIAN && V8_OS_LINUX const uint8_t ident[16] = {0x7F, 'E', 'L', 'F', 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #elif V8_TARGET_ARCH_S390X const uint8_t ident[16] = {0x7F, 'E', 'L', 'F', 2, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0}; #elif V8_TARGET_ARCH_S390 const uint8_t ident[16] = {0x7F, 'E', 'L', 'F', 1, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0}; #else #error Unsupported target architecture. #endif memcpy(header->ident, ident, 16); header->type = 1; #if V8_TARGET_ARCH_IA32 header->machine = 3; #elif V8_TARGET_ARCH_X64 // 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 V8_TARGET_ARCH_ARM // 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 V8_TARGET_ARCH_PPC64 && V8_OS_LINUX // Set to EM_PPC64, defined as 21, in Power ABI, // Join the next 4 lines, omitting the spaces and double-slashes. // https://www-03.ibm.com/technologyconnect/tgcm/TGCMFileServlet.wss/ // ABI64BitOpenPOWERv1.1_16July2015_pub.pdf? // id=B81AEC1A37F5DAF185257C3E004E8845&linkid=1n0000&c_t= // c9xw7v5dzsj7gt1ifgf4cjbcnskqptmr header->machine = 21; #elif V8_TARGET_ARCH_S390 // Processor identification value is 22 (EM_S390) as defined in the ABI: // http://refspecs.linuxbase.org/ELF/zSeries/lzsabi0_s390.html#AEN1691 // http://refspecs.linuxbase.org/ELF/zSeries/lzsabi0_zSeries.html#AEN1599 header->machine = 22; #else #error Unsupported target architecture. #endif header->version = 1; header->entry = 0; header->pht_offset = 0; header->sht_offset = sizeof(ELFHeader); // Section table follows header. header->flags = 0; header->header_size = sizeof(ELFHeader); header->pht_entry_size = 0; header->pht_entry_num = 0; header->sht_entry_size = sizeof(ELFSection::Header); header->sht_entry_num = sections_.size(); header->sht_strtab_index = 1; } void WriteSectionTable(Writer* w) { // Section headers table immediately follows file header. DCHECK(w->position() == sizeof(ELFHeader)); Writer::Slot headers = w->CreateSlotsHere( static_cast(sections_.size())); // String table for section table is the first section. ELFStringTable* strtab = static_cast(SectionAt(1)); strtab->AttachWriter(w); uint32_t index = 0; for (ELFSection* section : sections_) { section->PopulateHeader(headers.at(index), strtab); index++; } strtab->DetachWriter(); } int SectionHeaderPosition(uint32_t section_index) { return sizeof(ELFHeader) + sizeof(ELFSection::Header) * section_index; } void WriteSections(Writer* w) { Writer::Slot headers = w->SlotAt(sizeof(ELFHeader)); uint32_t index = 0; for (ELFSection* section : sections_) { section->WriteBody(headers.at(index), w); index++; } } ZoneChunkList sections_; }; class ELFSymbol { public: enum Type { TYPE_NOTYPE = 0, TYPE_OBJECT = 1, TYPE_FUNC = 2, TYPE_SECTION = 3, TYPE_FILE = 4, TYPE_LOPROC = 13, TYPE_HIPROC = 15 }; enum Binding { BIND_LOCAL = 0, BIND_GLOBAL = 1, BIND_WEAK = 2, BIND_LOPROC = 13, BIND_HIPROC = 15 }; ELFSymbol(const char* name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) : name(name), value(value), size(size), info((binding << 4) | type), other(0), section(section) { } Binding binding() const { return static_cast(info >> 4); } #if (V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_ARM || \ (V8_TARGET_ARCH_X64 && V8_TARGET_ARCH_32_BIT) || \ (V8_TARGET_ARCH_S390 && V8_TARGET_ARCH_32_BIT)) struct SerializedLayout { SerializedLayout(uint32_t name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) : name(name), value(value), size(size), info((binding << 4) | type), other(0), section(section) { } uint32_t name; uintptr_t value; uintptr_t size; uint8_t info; uint8_t other; uint16_t section; }; #elif(V8_TARGET_ARCH_X64 && V8_TARGET_ARCH_64_BIT) || \ (V8_TARGET_ARCH_PPC64 && V8_OS_LINUX) || V8_TARGET_ARCH_S390X struct SerializedLayout { SerializedLayout(uint32_t name, uintptr_t value, uintptr_t size, Binding binding, Type type, uint16_t section) : name(name), info((binding << 4) | type), other(0), section(section), value(value), size(size) { } uint32_t name; uint8_t info; uint8_t other; uint16_t section; uintptr_t value; uintptr_t size; }; #endif void Write(Writer::Slot s, ELFStringTable* t) const { // Convert symbol names from strings to indexes in the string table. s->name = static_cast(t->Add(name)); s->value = value; s->size = size; s->info = info; s->other = other; s->section = section; } private: const char* name; uintptr_t value; uintptr_t size; uint8_t info; uint8_t other; uint16_t section; }; class ELFSymbolTable : public ELFSection { public: ELFSymbolTable(const char* name, Zone* zone) : ELFSection(name, TYPE_SYMTAB, sizeof(uintptr_t)), locals_(zone), globals_(zone) {} void WriteBody(Writer::Slot
header, Writer* w) override { w->Align(header->alignment); size_t total_symbols = locals_.size() + globals_.size() + 1; header->offset = w->position(); Writer::Slot symbols = w->CreateSlotsHere( static_cast(total_symbols)); header->size = w->position() - header->offset; // String table for this symbol table should follow it in the section table. ELFStringTable* strtab = static_cast(w->debug_object()->SectionAt(index() + 1)); strtab->AttachWriter(w); symbols.at(0).set(ELFSymbol::SerializedLayout(0, 0, 0, ELFSymbol::BIND_LOCAL, ELFSymbol::TYPE_NOTYPE, 0)); WriteSymbolsList(&locals_, symbols.at(1), strtab); WriteSymbolsList(&globals_, symbols.at(static_cast(locals_.size() + 1)), strtab); strtab->DetachWriter(); } void Add(const ELFSymbol& symbol) { if (symbol.binding() == ELFSymbol::BIND_LOCAL) { locals_.push_back(symbol); } else { globals_.push_back(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(locals_.size() + 1); header->entry_size = sizeof(ELFSymbol::SerializedLayout); } private: void WriteSymbolsList(const ZoneChunkList* src, Writer::Slot dst, ELFStringTable* strtab) { int i = 0; for (const ELFSymbol& symbol : *src) { symbol.Write(dst.at(i++), strtab); } } ZoneChunkList locals_; ZoneChunkList globals_; }; #endif // defined(__ELF) class LineInfo : public Malloced { public: void SetPosition(intptr_t pc, int pos, bool is_statement) { AddPCInfo(PCInfo(pc, pos, is_statement)); } struct PCInfo { PCInfo(intptr_t pc, int pos, bool is_statement) : pc_(pc), pos_(pos), is_statement_(is_statement) {} intptr_t pc_; int pos_; bool is_statement_; }; std::vector* pc_info() { return &pc_info_; } private: void AddPCInfo(const PCInfo& pc_info) { pc_info_.push_back(pc_info); } std::vector pc_info_; }; class CodeDescription { public: #if V8_TARGET_ARCH_X64 enum StackState { POST_RBP_PUSH, POST_RBP_SET, POST_RBP_POP, STACK_STATE_MAX }; #endif CodeDescription(const char* name, Code* code, SharedFunctionInfo* shared, LineInfo* lineinfo) : name_(name), code_(code), shared_info_(shared), lineinfo_(lineinfo) {} const char* name() const { return name_; } LineInfo* lineinfo() const { return lineinfo_; } bool is_function() const { Code::Kind kind = code_->kind(); return kind == Code::OPTIMIZED_FUNCTION; } bool has_scope_info() const { return shared_info_ != nullptr; } ScopeInfo* scope_info() const { DCHECK(has_scope_info()); return shared_info_->scope_info(); } uintptr_t CodeStart() const { return static_cast(code_->InstructionStart()); } uintptr_t CodeEnd() const { return static_cast(code_->InstructionEnd()); } uintptr_t CodeSize() const { return CodeEnd() - CodeStart(); } bool has_script() { return shared_info_ != nullptr && shared_info_->script()->IsScript(); } Script* script() { return Script::cast(shared_info_->script()); } bool IsLineInfoAvailable() { return lineinfo_ != nullptr; } #if V8_TARGET_ARCH_X64 uintptr_t GetStackStateStartAddress(StackState state) const { DCHECK(state < STACK_STATE_MAX); return stack_state_start_addresses_[state]; } void SetStackStateStartAddress(StackState state, uintptr_t addr) { DCHECK(state < STACK_STATE_MAX); stack_state_start_addresses_[state] = addr; } #endif std::unique_ptr GetFilename() { if (shared_info_ != nullptr) { return String::cast(script()->name())->ToCString(); } else { std::unique_ptr result(new char[1]); result[0] = 0; return result; } } int GetScriptLineNumber(int pos) { if (shared_info_ != nullptr) { return script()->GetLineNumber(pos) + 1; } else { return 0; } } private: const char* name_; Code* code_; SharedFunctionInfo* shared_info_; LineInfo* lineinfo_; #if V8_TARGET_ARCH_X64 uintptr_t stack_state_start_addresses_[STACK_STATE_MAX]; #endif }; #if defined(__ELF) static void CreateSymbolsTable(CodeDescription* desc, Zone* zone, ELF* elf, size_t text_section_index) { ELFSymbolTable* symtab = new(zone) ELFSymbolTable(".symtab", zone); ELFStringTable* strtab = new(zone) ELFStringTable(".strtab"); // Symbol table should be followed by the linked string table. elf->AddSection(symtab); elf->AddSection(strtab); symtab->Add(ELFSymbol("V8 Code", 0, 0, ELFSymbol::BIND_LOCAL, ELFSymbol::TYPE_FILE, ELFSection::INDEX_ABSOLUTE)); symtab->Add(ELFSymbol(desc->name(), 0, desc->CodeSize(), ELFSymbol::BIND_GLOBAL, ELFSymbol::TYPE_FUNC, text_section_index)); } #endif // defined(__ELF) class DebugInfoSection : public DebugSection { public: explicit DebugInfoSection(CodeDescription* desc) #if defined(__ELF) : ELFSection(".debug_info", TYPE_PROGBITS, 1), #else : MachOSection("__debug_info", "__DWARF", 1, MachOSection::S_REGULAR | MachOSection::S_ATTR_DEBUG), #endif desc_(desc) { } // DWARF2 standard enum DWARF2LocationOp { DW_OP_reg0 = 0x50, DW_OP_reg1 = 0x51, DW_OP_reg2 = 0x52, DW_OP_reg3 = 0x53, DW_OP_reg4 = 0x54, DW_OP_reg5 = 0x55, DW_OP_reg6 = 0x56, DW_OP_reg7 = 0x57, DW_OP_reg8 = 0x58, DW_OP_reg9 = 0x59, DW_OP_reg10 = 0x5A, DW_OP_reg11 = 0x5B, DW_OP_reg12 = 0x5C, DW_OP_reg13 = 0x5D, DW_OP_reg14 = 0x5E, DW_OP_reg15 = 0x5F, DW_OP_reg16 = 0x60, DW_OP_reg17 = 0x61, DW_OP_reg18 = 0x62, DW_OP_reg19 = 0x63, DW_OP_reg20 = 0x64, DW_OP_reg21 = 0x65, DW_OP_reg22 = 0x66, DW_OP_reg23 = 0x67, DW_OP_reg24 = 0x68, DW_OP_reg25 = 0x69, DW_OP_reg26 = 0x6A, DW_OP_reg27 = 0x6B, DW_OP_reg28 = 0x6C, DW_OP_reg29 = 0x6D, DW_OP_reg30 = 0x6E, DW_OP_reg31 = 0x6F, DW_OP_fbreg = 0x91 // 1 param: SLEB128 offset }; enum DWARF2Encoding { DW_ATE_ADDRESS = 0x1, DW_ATE_SIGNED = 0x5 }; bool WriteBodyInternal(Writer* w) override { uintptr_t cu_start = w->position(); Writer::Slot size = w->CreateSlotHere(); uintptr_t start = w->position(); w->Write(2); // DWARF version. w->Write(0); // Abbreviation table offset. w->Write(sizeof(intptr_t)); w->WriteULEB128(1); // Abbreviation code. w->WriteString(desc_->GetFilename().get()); w->Write(desc_->CodeStart()); w->Write(desc_->CodeStart() + desc_->CodeSize()); w->Write(0); uint32_t ty_offset = static_cast(w->position() - cu_start); w->WriteULEB128(3); w->Write(kPointerSize); w->WriteString("v8value"); if (desc_->has_scope_info()) { ScopeInfo* scope = desc_->scope_info(); w->WriteULEB128(2); w->WriteString(desc_->name()); w->Write(desc_->CodeStart()); w->Write(desc_->CodeStart() + desc_->CodeSize()); Writer::Slot fb_block_size = w->CreateSlotHere(); uintptr_t fb_block_start = w->position(); #if V8_TARGET_ARCH_IA32 w->Write(DW_OP_reg5); // The frame pointer's here on ia32 #elif V8_TARGET_ARCH_X64 w->Write(DW_OP_reg6); // and here on x64. #elif V8_TARGET_ARCH_ARM UNIMPLEMENTED(); #elif V8_TARGET_ARCH_MIPS UNIMPLEMENTED(); #elif V8_TARGET_ARCH_MIPS64 UNIMPLEMENTED(); #elif V8_TARGET_ARCH_PPC64 && V8_OS_LINUX w->Write(DW_OP_reg31); // The frame pointer is here on PPC64. #elif V8_TARGET_ARCH_S390 w->Write(DW_OP_reg11); // The frame pointer's here on S390. #else #error Unsupported target architecture. #endif fb_block_size.set(static_cast(w->position() - fb_block_start)); int params = scope->ParameterCount(); int context_slots = scope->ContextLocalCount(); // The real slot ID is internal_slots + context_slot_id. int internal_slots = Context::MIN_CONTEXT_SLOTS; int current_abbreviation = 4; EmbeddedVector buffer; StringBuilder builder(buffer.start(), buffer.length()); for (int param = 0; param < params; ++param) { w->WriteULEB128(current_abbreviation++); builder.Reset(); builder.AddFormatted("param%d", param); w->WriteString(builder.Finalize()); w->Write(ty_offset); Writer::Slot block_size = w->CreateSlotHere(); uintptr_t block_start = w->position(); w->Write(DW_OP_fbreg); w->WriteSLEB128( JavaScriptFrameConstants::kLastParameterOffset + kPointerSize * (params - param - 1)); block_size.set(static_cast(w->position() - block_start)); } // See contexts.h for more information. DCHECK_EQ(Context::MIN_CONTEXT_SLOTS, 4); DCHECK_EQ(Context::SCOPE_INFO_INDEX, 0); DCHECK_EQ(Context::PREVIOUS_INDEX, 1); DCHECK_EQ(Context::EXTENSION_INDEX, 2); DCHECK_EQ(Context::NATIVE_CONTEXT_INDEX, 3); w->WriteULEB128(current_abbreviation++); w->WriteString(".scope_info"); w->WriteULEB128(current_abbreviation++); w->WriteString(".previous"); w->WriteULEB128(current_abbreviation++); w->WriteString(".extension"); w->WriteULEB128(current_abbreviation++); w->WriteString(".native_context"); for (int context_slot = 0; context_slot < context_slots; ++context_slot) { w->WriteULEB128(current_abbreviation++); builder.Reset(); builder.AddFormatted("context_slot%d", context_slot + internal_slots); w->WriteString(builder.Finalize()); } { w->WriteULEB128(current_abbreviation++); w->WriteString("__function"); w->Write(ty_offset); Writer::Slot block_size = w->CreateSlotHere(); uintptr_t block_start = w->position(); w->Write(DW_OP_fbreg); w->WriteSLEB128(JavaScriptFrameConstants::kFunctionOffset); block_size.set(static_cast(w->position() - block_start)); } { w->WriteULEB128(current_abbreviation++); w->WriteString("__context"); w->Write(ty_offset); Writer::Slot block_size = w->CreateSlotHere(); uintptr_t block_start = w->position(); w->Write(DW_OP_fbreg); w->WriteSLEB128(StandardFrameConstants::kContextOffset); block_size.set(static_cast(w->position() - block_start)); } w->WriteULEB128(0); // Terminate the sub program. } w->WriteULEB128(0); // Terminate the compile unit. size.set(static_cast(w->position() - start)); return true; } private: CodeDescription* desc_; }; class DebugAbbrevSection : public DebugSection { public: explicit DebugAbbrevSection(CodeDescription* desc) #ifdef __ELF : ELFSection(".debug_abbrev", TYPE_PROGBITS, 1), #else : MachOSection("__debug_abbrev", "__DWARF", 1, MachOSection::S_REGULAR | MachOSection::S_ATTR_DEBUG), #endif desc_(desc) { } // DWARF2 standard, figure 14. enum DWARF2Tags { DW_TAG_FORMAL_PARAMETER = 0x05, DW_TAG_POINTER_TYPE = 0xF, DW_TAG_COMPILE_UNIT = 0x11, DW_TAG_STRUCTURE_TYPE = 0x13, DW_TAG_BASE_TYPE = 0x24, DW_TAG_SUBPROGRAM = 0x2E, DW_TAG_VARIABLE = 0x34 }; // DWARF2 standard, figure 16. enum DWARF2ChildrenDetermination { DW_CHILDREN_NO = 0, DW_CHILDREN_YES = 1 }; // DWARF standard, figure 17. enum DWARF2Attribute { DW_AT_LOCATION = 0x2, DW_AT_NAME = 0x3, DW_AT_BYTE_SIZE = 0xB, DW_AT_STMT_LIST = 0x10, DW_AT_LOW_PC = 0x11, DW_AT_HIGH_PC = 0x12, DW_AT_ENCODING = 0x3E, DW_AT_FRAME_BASE = 0x40, DW_AT_TYPE = 0x49 }; // DWARF2 standard, figure 19. enum DWARF2AttributeForm { DW_FORM_ADDR = 0x1, DW_FORM_BLOCK4 = 0x4, DW_FORM_STRING = 0x8, DW_FORM_DATA4 = 0x6, DW_FORM_BLOCK = 0x9, DW_FORM_DATA1 = 0xB, DW_FORM_FLAG = 0xC, DW_FORM_REF4 = 0x13 }; void WriteVariableAbbreviation(Writer* w, int abbreviation_code, bool has_value, bool is_parameter) { w->WriteULEB128(abbreviation_code); w->WriteULEB128(is_parameter ? DW_TAG_FORMAL_PARAMETER : DW_TAG_VARIABLE); w->Write(DW_CHILDREN_NO); w->WriteULEB128(DW_AT_NAME); w->WriteULEB128(DW_FORM_STRING); if (has_value) { w->WriteULEB128(DW_AT_TYPE); w->WriteULEB128(DW_FORM_REF4); w->WriteULEB128(DW_AT_LOCATION); w->WriteULEB128(DW_FORM_BLOCK4); } w->WriteULEB128(0); w->WriteULEB128(0); } bool WriteBodyInternal(Writer* w) override { int current_abbreviation = 1; bool extra_info = desc_->has_scope_info(); DCHECK(desc_->IsLineInfoAvailable()); w->WriteULEB128(current_abbreviation++); w->WriteULEB128(DW_TAG_COMPILE_UNIT); w->Write(extra_info ? DW_CHILDREN_YES : DW_CHILDREN_NO); w->WriteULEB128(DW_AT_NAME); w->WriteULEB128(DW_FORM_STRING); w->WriteULEB128(DW_AT_LOW_PC); w->WriteULEB128(DW_FORM_ADDR); w->WriteULEB128(DW_AT_HIGH_PC); w->WriteULEB128(DW_FORM_ADDR); w->WriteULEB128(DW_AT_STMT_LIST); w->WriteULEB128(DW_FORM_DATA4); w->WriteULEB128(0); w->WriteULEB128(0); if (extra_info) { ScopeInfo* scope = desc_->scope_info(); int params = scope->ParameterCount(); int context_slots = scope->ContextLocalCount(); // The real slot ID is internal_slots + context_slot_id. int internal_slots = Context::MIN_CONTEXT_SLOTS; // Total children is params + context_slots + internal_slots + 2 // (__function and __context). // The extra duplication below seems to be necessary to keep // gdb from getting upset on OSX. w->WriteULEB128(current_abbreviation++); // Abbreviation code. w->WriteULEB128(DW_TAG_SUBPROGRAM); w->Write(DW_CHILDREN_YES); w->WriteULEB128(DW_AT_NAME); w->WriteULEB128(DW_FORM_STRING); w->WriteULEB128(DW_AT_LOW_PC); w->WriteULEB128(DW_FORM_ADDR); w->WriteULEB128(DW_AT_HIGH_PC); w->WriteULEB128(DW_FORM_ADDR); w->WriteULEB128(DW_AT_FRAME_BASE); w->WriteULEB128(DW_FORM_BLOCK4); w->WriteULEB128(0); w->WriteULEB128(0); w->WriteULEB128(current_abbreviation++); w->WriteULEB128(DW_TAG_STRUCTURE_TYPE); w->Write(DW_CHILDREN_NO); w->WriteULEB128(DW_AT_BYTE_SIZE); w->WriteULEB128(DW_FORM_DATA1); w->WriteULEB128(DW_AT_NAME); w->WriteULEB128(DW_FORM_STRING); w->WriteULEB128(0); w->WriteULEB128(0); for (int param = 0; param < params; ++param) { WriteVariableAbbreviation(w, current_abbreviation++, true, true); } for (int internal_slot = 0; internal_slot < internal_slots; ++internal_slot) { WriteVariableAbbreviation(w, current_abbreviation++, false, false); } for (int context_slot = 0; context_slot < context_slots; ++context_slot) { WriteVariableAbbreviation(w, current_abbreviation++, false, false); } // The function. WriteVariableAbbreviation(w, current_abbreviation++, true, false); // The context. WriteVariableAbbreviation(w, current_abbreviation++, true, false); w->WriteULEB128(0); // Terminate the sibling list. } w->WriteULEB128(0); // Terminate the table. return true; } private: CodeDescription* desc_; }; class DebugLineSection : public DebugSection { public: explicit DebugLineSection(CodeDescription* desc) #ifdef __ELF : ELFSection(".debug_line", TYPE_PROGBITS, 1), #else : MachOSection("__debug_line", "__DWARF", 1, MachOSection::S_REGULAR | MachOSection::S_ATTR_DEBUG), #endif desc_(desc) { } // DWARF2 standard, figure 34. enum DWARF2Opcodes { DW_LNS_COPY = 1, DW_LNS_ADVANCE_PC = 2, DW_LNS_ADVANCE_LINE = 3, DW_LNS_SET_FILE = 4, DW_LNS_SET_COLUMN = 5, DW_LNS_NEGATE_STMT = 6 }; // DWARF2 standard, figure 35. enum DWARF2ExtendedOpcode { DW_LNE_END_SEQUENCE = 1, DW_LNE_SET_ADDRESS = 2, DW_LNE_DEFINE_FILE = 3 }; bool WriteBodyInternal(Writer* w) override { // Write prologue. Writer::Slot total_length = w->CreateSlotHere(); uintptr_t start = w->position(); // Used for special opcodes const int8_t line_base = 1; const uint8_t line_range = 7; const int8_t max_line_incr = (line_base + line_range - 1); const uint8_t opcode_base = DW_LNS_NEGATE_STMT + 1; w->Write(2); // Field version. Writer::Slot prologue_length = w->CreateSlotHere(); uintptr_t prologue_start = w->position(); w->Write(1); // Field minimum_instruction_length. w->Write(1); // Field default_is_stmt. w->Write(line_base); // Field line_base. w->Write(line_range); // Field line_range. w->Write(opcode_base); // Field opcode_base. w->Write(0); // DW_LNS_COPY operands count. w->Write(1); // DW_LNS_ADVANCE_PC operands count. w->Write(1); // DW_LNS_ADVANCE_LINE operands count. w->Write(1); // DW_LNS_SET_FILE operands count. w->Write(1); // DW_LNS_SET_COLUMN operands count. w->Write(0); // DW_LNS_NEGATE_STMT operands count. w->Write(0); // Empty include_directories sequence. w->WriteString(desc_->GetFilename().get()); // File name. w->WriteULEB128(0); // Current directory. w->WriteULEB128(0); // Unknown modification time. w->WriteULEB128(0); // Unknown file size. w->Write(0); prologue_length.set(static_cast(w->position() - prologue_start)); WriteExtendedOpcode(w, DW_LNE_SET_ADDRESS, sizeof(intptr_t)); w->Write(desc_->CodeStart()); w->Write(DW_LNS_COPY); intptr_t pc = 0; intptr_t line = 1; bool is_statement = true; std::vector* pc_info = desc_->lineinfo()->pc_info(); std::sort(pc_info->begin(), pc_info->end(), &ComparePCInfo); for (size_t i = 0; i < pc_info->size(); i++) { LineInfo::PCInfo* info = &pc_info->at(i); DCHECK(info->pc_ >= pc); // Reduce bloating in the debug line table by removing duplicate line // entries (per DWARF2 standard). intptr_t new_line = desc_->GetScriptLineNumber(info->pos_); if (new_line == line) { continue; } // Mark statement boundaries. For a better debugging experience, mark // the last pc address in the function as a statement (e.g. "}"), so that // a user can see the result of the last line executed in the function, // should control reach the end. if ((i + 1) == pc_info->size()) { if (!is_statement) { w->Write(DW_LNS_NEGATE_STMT); } } else if (is_statement != info->is_statement_) { w->Write(DW_LNS_NEGATE_STMT); is_statement = !is_statement; } // Generate special opcodes, if possible. This results in more compact // debug line tables. See the DWARF 2.0 standard to learn more about // special opcodes. uintptr_t pc_diff = info->pc_ - pc; intptr_t line_diff = new_line - line; // Compute special opcode (see DWARF 2.0 standard) intptr_t special_opcode = (line_diff - line_base) + (line_range * pc_diff) + opcode_base; // If special_opcode is less than or equal to 255, it can be used as a // special opcode. If line_diff is larger than the max line increment // allowed for a special opcode, or if line_diff is less than the minimum // line that can be added to the line register (i.e. line_base), then // special_opcode can't be used. if ((special_opcode >= opcode_base) && (special_opcode <= 255) && (line_diff <= max_line_incr) && (line_diff >= line_base)) { w->Write(special_opcode); } else { w->Write(DW_LNS_ADVANCE_PC); w->WriteSLEB128(pc_diff); w->Write(DW_LNS_ADVANCE_LINE); w->WriteSLEB128(line_diff); w->Write(DW_LNS_COPY); } // Increment the pc and line operands. pc += pc_diff; line += line_diff; } // Advance the pc to the end of the routine, since the end sequence opcode // requires this. w->Write(DW_LNS_ADVANCE_PC); w->WriteSLEB128(desc_->CodeSize() - pc); WriteExtendedOpcode(w, DW_LNE_END_SEQUENCE, 0); total_length.set(static_cast(w->position() - start)); return true; } private: void WriteExtendedOpcode(Writer* w, DWARF2ExtendedOpcode op, size_t operands_size) { w->Write(0); w->WriteULEB128(operands_size + 1); w->Write(op); } static bool ComparePCInfo(const LineInfo::PCInfo& a, const LineInfo::PCInfo& b) { if (a.pc_ == b.pc_) { if (a.is_statement_ != b.is_statement_) { return !b.is_statement_; } return false; } return a.pc_ < b.pc_; } CodeDescription* desc_; }; #if V8_TARGET_ARCH_X64 class UnwindInfoSection : public DebugSection { public: explicit UnwindInfoSection(CodeDescription* desc); bool WriteBodyInternal(Writer* w) override; int WriteCIE(Writer* w); void WriteFDE(Writer* w, int); void WriteFDEStateOnEntry(Writer* w); void WriteFDEStateAfterRBPPush(Writer* w); void WriteFDEStateAfterRBPSet(Writer* w); void WriteFDEStateAfterRBPPop(Writer* w); void WriteLength(Writer* w, Writer::Slot* length_slot, int initial_position); private: CodeDescription* desc_; // DWARF3 Specification, Table 7.23 enum CFIInstructions { DW_CFA_ADVANCE_LOC = 0x40, DW_CFA_OFFSET = 0x80, DW_CFA_RESTORE = 0xC0, DW_CFA_NOP = 0x00, DW_CFA_SET_LOC = 0x01, DW_CFA_ADVANCE_LOC1 = 0x02, DW_CFA_ADVANCE_LOC2 = 0x03, DW_CFA_ADVANCE_LOC4 = 0x04, DW_CFA_OFFSET_EXTENDED = 0x05, DW_CFA_RESTORE_EXTENDED = 0x06, DW_CFA_UNDEFINED = 0x07, DW_CFA_SAME_VALUE = 0x08, DW_CFA_REGISTER = 0x09, DW_CFA_REMEMBER_STATE = 0x0A, DW_CFA_RESTORE_STATE = 0x0B, DW_CFA_DEF_CFA = 0x0C, DW_CFA_DEF_CFA_REGISTER = 0x0D, DW_CFA_DEF_CFA_OFFSET = 0x0E, DW_CFA_DEF_CFA_EXPRESSION = 0x0F, DW_CFA_EXPRESSION = 0x10, DW_CFA_OFFSET_EXTENDED_SF = 0x11, DW_CFA_DEF_CFA_SF = 0x12, DW_CFA_DEF_CFA_OFFSET_SF = 0x13, DW_CFA_VAL_OFFSET = 0x14, DW_CFA_VAL_OFFSET_SF = 0x15, DW_CFA_VAL_EXPRESSION = 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. AMD64_RBP = 6, AMD64_RSP = 7, AMD64_RA = 16 }; enum CFIConstants { CIE_ID = 0, CIE_VERSION = 1, CODE_ALIGN_FACTOR = 1, DATA_ALIGN_FACTOR = 1, RETURN_ADDRESS_REGISTER = AMD64_RA }; }; void UnwindInfoSection::WriteLength(Writer* w, Writer::Slot* length_slot, int initial_position) { uint32_t align = (w->position() - initial_position) % kPointerSize; if (align != 0) { for (uint32_t i = 0; i < (kPointerSize - align); i++) { w->Write(DW_CFA_NOP); } } DCHECK_EQ((w->position() - initial_position) % kPointerSize, 0); length_slot->set(static_cast(w->position() - initial_position)); } UnwindInfoSection::UnwindInfoSection(CodeDescription* desc) #ifdef __ELF : ELFSection(".eh_frame", TYPE_X86_64_UNWIND, 1), #else : MachOSection("__eh_frame", "__TEXT", sizeof(uintptr_t), MachOSection::S_REGULAR), #endif desc_(desc) { } int UnwindInfoSection::WriteCIE(Writer* w) { Writer::Slot cie_length_slot = w->CreateSlotHere(); uint32_t cie_position = static_cast(w->position()); // Write out the CIE header. Currently no 'common instructions' are // emitted onto the CIE; every FDE has its own set of instructions. w->Write(CIE_ID); w->Write(CIE_VERSION); w->Write(0); // Null augmentation string. w->WriteSLEB128(CODE_ALIGN_FACTOR); w->WriteSLEB128(DATA_ALIGN_FACTOR); w->Write(RETURN_ADDRESS_REGISTER); WriteLength(w, &cie_length_slot, cie_position); return cie_position; } void UnwindInfoSection::WriteFDE(Writer* w, int cie_position) { // The only FDE for this function. The CFA is the current RBP. Writer::Slot fde_length_slot = w->CreateSlotHere(); int fde_position = static_cast(w->position()); w->Write(fde_position - cie_position + 4); w->Write(desc_->CodeStart()); w->Write(desc_->CodeSize()); WriteFDEStateOnEntry(w); WriteFDEStateAfterRBPPush(w); WriteFDEStateAfterRBPSet(w); WriteFDEStateAfterRBPPop(w); WriteLength(w, &fde_length_slot, fde_position); } void UnwindInfoSection::WriteFDEStateOnEntry(Writer* w) { // The first state, just after the control has been transferred to the the // function. // RBP for this function will be the value of RSP after pushing the RBP // for the previous function. The previous RBP has not been pushed yet. w->Write(DW_CFA_DEF_CFA_SF); w->WriteULEB128(AMD64_RSP); w->WriteSLEB128(-kPointerSize); // The RA is stored at location CFA + kCallerPCOffset. This is an invariant, // and hence omitted from the next states. w->Write(DW_CFA_OFFSET_EXTENDED); w->WriteULEB128(AMD64_RA); w->WriteSLEB128(StandardFrameConstants::kCallerPCOffset); // The RBP of the previous function is still in RBP. w->Write(DW_CFA_SAME_VALUE); w->WriteULEB128(AMD64_RBP); // Last location described by this entry. w->Write(DW_CFA_SET_LOC); w->Write( desc_->GetStackStateStartAddress(CodeDescription::POST_RBP_PUSH)); } void UnwindInfoSection::WriteFDEStateAfterRBPPush(Writer* w) { // The second state, just after RBP has been pushed. // RBP / CFA for this function is now the current RSP, so just set the // offset from the previous rule (from -8) to 0. w->Write(DW_CFA_DEF_CFA_OFFSET); w->WriteULEB128(0); // The previous RBP is stored at CFA + kCallerFPOffset. This is an invariant // in this and the next state, and hence omitted in the next state. w->Write(DW_CFA_OFFSET_EXTENDED); w->WriteULEB128(AMD64_RBP); w->WriteSLEB128(StandardFrameConstants::kCallerFPOffset); // Last location described by this entry. w->Write(DW_CFA_SET_LOC); w->Write( desc_->GetStackStateStartAddress(CodeDescription::POST_RBP_SET)); } void UnwindInfoSection::WriteFDEStateAfterRBPSet(Writer* w) { // The third state, after the RBP has been set. // The CFA can now directly be set to RBP. w->Write(DW_CFA_DEF_CFA); w->WriteULEB128(AMD64_RBP); w->WriteULEB128(0); // Last location described by this entry. w->Write(DW_CFA_SET_LOC); w->Write( desc_->GetStackStateStartAddress(CodeDescription::POST_RBP_POP)); } void UnwindInfoSection::WriteFDEStateAfterRBPPop(Writer* w) { // The fourth (final) state. The RBP has been popped (just before issuing a // return). // The CFA can is now calculated in the same way as in the first state. w->Write(DW_CFA_DEF_CFA_SF); w->WriteULEB128(AMD64_RSP); w->WriteSLEB128(-kPointerSize); // The RBP w->Write(DW_CFA_OFFSET_EXTENDED); w->WriteULEB128(AMD64_RBP); w->WriteSLEB128(StandardFrameConstants::kCallerFPOffset); // Last location described by this entry. w->Write(DW_CFA_SET_LOC); w->Write(desc_->CodeEnd()); } bool UnwindInfoSection::WriteBodyInternal(Writer* w) { uint32_t cie_position = WriteCIE(w); WriteFDE(w, cie_position); return true; } #endif // V8_TARGET_ARCH_X64 static void CreateDWARFSections(CodeDescription* desc, Zone* zone, DebugObject* obj) { if (desc->IsLineInfoAvailable()) { obj->AddSection(new(zone) DebugInfoSection(desc)); obj->AddSection(new(zone) DebugAbbrevSection(desc)); obj->AddSection(new(zone) DebugLineSection(desc)); } #if V8_TARGET_ARCH_X64 obj->AddSection(new(zone) UnwindInfoSection(desc)); #endif } // ------------------------------------------------------------------- // Binary GDB JIT Interface as described in // http://sourceware.org/gdb/onlinedocs/gdb/Declarations.html extern "C" { typedef enum { JIT_NOACTION = 0, JIT_REGISTER_FN, JIT_UNREGISTER_FN } JITAction; struct JITCodeEntry { JITCodeEntry* next_; JITCodeEntry* prev_; Address symfile_addr_; uint64_t symfile_size_; }; struct JITDescriptor { uint32_t version_; uint32_t action_flag_; JITCodeEntry* relevant_entry_; JITCodeEntry* first_entry_; }; // GDB will place breakpoint into this function. // To prevent GCC from inlining or removing it we place noinline attribute // and inline assembler statement inside. void __attribute__((noinline)) __jit_debug_register_code() { __asm__(""); } // GDB will inspect contents of this descriptor. // Static initialization is necessary to prevent GDB from seeing // uninitialized descriptor. JITDescriptor __jit_debug_descriptor = {1, 0, nullptr, nullptr}; #ifdef OBJECT_PRINT void __gdb_print_v8_object(Object* object) { StdoutStream os; object->Print(os); os << std::flush; } #endif } static JITCodeEntry* CreateCodeEntry(Address symfile_addr, uintptr_t symfile_size) { JITCodeEntry* entry = static_cast( malloc(sizeof(JITCodeEntry) + symfile_size)); entry->symfile_addr_ = reinterpret_cast
(entry + 1); entry->symfile_size_ = symfile_size; MemCopy(reinterpret_cast(entry->symfile_addr_), reinterpret_cast(symfile_addr), symfile_size); entry->prev_ = entry->next_ = nullptr; return entry; } static void DestroyCodeEntry(JITCodeEntry* entry) { free(entry); } static void RegisterCodeEntry(JITCodeEntry* entry) { entry->next_ = __jit_debug_descriptor.first_entry_; if (entry->next_ != nullptr) entry->next_->prev_ = entry; __jit_debug_descriptor.first_entry_ = __jit_debug_descriptor.relevant_entry_ = entry; __jit_debug_descriptor.action_flag_ = JIT_REGISTER_FN; __jit_debug_register_code(); } static void UnregisterCodeEntry(JITCodeEntry* entry) { if (entry->prev_ != nullptr) { entry->prev_->next_ = entry->next_; } else { __jit_debug_descriptor.first_entry_ = entry->next_; } if (entry->next_ != nullptr) { entry->next_->prev_ = entry->prev_; } __jit_debug_descriptor.relevant_entry_ = entry; __jit_debug_descriptor.action_flag_ = JIT_UNREGISTER_FN; __jit_debug_register_code(); } static JITCodeEntry* CreateELFObject(CodeDescription* desc, Isolate* isolate) { #ifdef __MACH_O Zone zone(isolate->allocator(), ZONE_NAME); MachO mach_o(&zone); Writer w(&mach_o); mach_o.AddSection(new(&zone) MachOTextSection(kCodeAlignment, desc->CodeStart(), desc->CodeSize())); CreateDWARFSections(desc, &zone, &mach_o); mach_o.Write(&w, desc->CodeStart(), desc->CodeSize()); #else Zone zone(isolate->allocator(), ZONE_NAME); ELF elf(&zone); Writer w(&elf); size_t text_section_index = elf.AddSection(new (&zone) FullHeaderELFSection( ".text", ELFSection::TYPE_NOBITS, kCodeAlignment, desc->CodeStart(), 0, desc->CodeSize(), ELFSection::FLAG_ALLOC | ELFSection::FLAG_EXEC)); CreateSymbolsTable(desc, &zone, &elf, text_section_index); CreateDWARFSections(desc, &zone, &elf); elf.Write(&w); #endif return CreateCodeEntry(reinterpret_cast
(w.buffer()), w.position()); } struct AddressRange { Address start; Address end; }; struct SplayTreeConfig { typedef AddressRange Key; typedef JITCodeEntry* Value; static const AddressRange kNoKey; static Value NoValue() { return nullptr; } static int Compare(const AddressRange& a, const AddressRange& b) { // ptrdiff_t probably doesn't fit in an int. if (a.start < b.start) return -1; if (a.start == b.start) return 0; return 1; } }; const AddressRange SplayTreeConfig::kNoKey = {0, 0}; typedef SplayTree CodeMap; static CodeMap* GetCodeMap() { static CodeMap* code_map = nullptr; if (code_map == nullptr) code_map = new CodeMap(); return code_map; } static uint32_t HashCodeAddress(Address addr) { static const uintptr_t kGoldenRatio = 2654435761u; return static_cast((addr >> kCodeAlignmentBits) * kGoldenRatio); } static base::HashMap* GetLineMap() { static base::HashMap* line_map = nullptr; if (line_map == nullptr) { line_map = new base::HashMap(); } return line_map; } static void PutLineInfo(Address addr, LineInfo* info) { base::HashMap* line_map = GetLineMap(); base::HashMap::Entry* e = line_map->LookupOrInsert( reinterpret_cast(addr), HashCodeAddress(addr)); if (e->value != nullptr) delete static_cast(e->value); e->value = info; } static LineInfo* GetLineInfo(Address addr) { void* value = GetLineMap()->Remove(reinterpret_cast(addr), HashCodeAddress(addr)); return static_cast(value); } static void AddUnwindInfo(CodeDescription* desc) { #if V8_TARGET_ARCH_X64 if (desc->is_function()) { // To avoid propagating unwinding information through // compilation pipeline we use an approximation. // For most use cases this should not affect usability. static const int kFramePointerPushOffset = 1; static const int kFramePointerSetOffset = 4; static const int kFramePointerPopOffset = -3; uintptr_t frame_pointer_push_address = desc->CodeStart() + kFramePointerPushOffset; uintptr_t frame_pointer_set_address = desc->CodeStart() + kFramePointerSetOffset; uintptr_t frame_pointer_pop_address = desc->CodeEnd() + kFramePointerPopOffset; desc->SetStackStateStartAddress(CodeDescription::POST_RBP_PUSH, frame_pointer_push_address); desc->SetStackStateStartAddress(CodeDescription::POST_RBP_SET, frame_pointer_set_address); desc->SetStackStateStartAddress(CodeDescription::POST_RBP_POP, frame_pointer_pop_address); } else { desc->SetStackStateStartAddress(CodeDescription::POST_RBP_PUSH, desc->CodeStart()); desc->SetStackStateStartAddress(CodeDescription::POST_RBP_SET, desc->CodeStart()); desc->SetStackStateStartAddress(CodeDescription::POST_RBP_POP, desc->CodeEnd()); } #endif // V8_TARGET_ARCH_X64 } static base::LazyMutex mutex = LAZY_MUTEX_INITIALIZER; // Remove entries from the splay tree that intersect the given address range, // and deregister them from GDB. static void RemoveJITCodeEntries(CodeMap* map, const AddressRange& range) { DCHECK(range.start < range.end); CodeMap::Locator cur; if (map->FindGreatestLessThan(range, &cur) || map->FindLeast(&cur)) { // Skip entries that are entirely less than the range of interest. while (cur.key().end <= range.start) { // CodeMap::FindLeastGreaterThan succeeds for entries whose key is greater // than _or equal to_ the given key, so we have to advance our key to get // the next one. AddressRange new_key; new_key.start = cur.key().end; new_key.end = 0; if (!map->FindLeastGreaterThan(new_key, &cur)) return; } // Evict intersecting ranges. while (cur.key().start < range.end) { AddressRange old_range = cur.key(); JITCodeEntry* old_entry = cur.value(); UnregisterCodeEntry(old_entry); DestroyCodeEntry(old_entry); CHECK(map->Remove(old_range)); if (!map->FindLeastGreaterThan(old_range, &cur)) return; } } } // Insert the entry into the splay tree and register it with GDB. static void AddJITCodeEntry(CodeMap* map, const AddressRange& range, JITCodeEntry* entry, bool dump_if_enabled, const char* name_hint) { #if defined(DEBUG) && !V8_OS_WIN static int file_num = 0; if (FLAG_gdbjit_dump && dump_if_enabled) { static const int kMaxFileNameSize = 64; char file_name[64]; SNPrintF(Vector(file_name, kMaxFileNameSize), "/tmp/elfdump%s%d.o", (name_hint != nullptr) ? name_hint : "", file_num++); WriteBytes(file_name, reinterpret_cast(entry->symfile_addr_), static_cast(entry->symfile_size_)); } #endif CodeMap::Locator cur; CHECK(map->Insert(range, &cur)); cur.set_value(entry); RegisterCodeEntry(entry); } static void AddCode(const char* name, Code* code, SharedFunctionInfo* shared, LineInfo* lineinfo) { DisallowHeapAllocation no_gc; CodeMap* code_map = GetCodeMap(); AddressRange range; range.start = code->address(); range.end = code->address() + code->CodeSize(); RemoveJITCodeEntries(code_map, range); CodeDescription code_desc(name, code, shared, lineinfo); if (!FLAG_gdbjit_full && !code_desc.IsLineInfoAvailable()) { delete lineinfo; return; } AddUnwindInfo(&code_desc); Isolate* isolate = code->GetIsolate(); JITCodeEntry* entry = CreateELFObject(&code_desc, isolate); delete lineinfo; const char* name_hint = nullptr; bool should_dump = false; if (FLAG_gdbjit_dump) { if (strlen(FLAG_gdbjit_dump_filter) == 0) { name_hint = name; should_dump = true; } else if (name != nullptr) { name_hint = strstr(name, FLAG_gdbjit_dump_filter); should_dump = (name_hint != nullptr); } } AddJITCodeEntry(code_map, range, entry, should_dump, name_hint); } void EventHandler(const v8::JitCodeEvent* event) { if (!FLAG_gdbjit) return; if (event->code_type != v8::JitCodeEvent::JIT_CODE) return; base::LockGuard lock_guard(mutex.Pointer()); switch (event->type) { case v8::JitCodeEvent::CODE_ADDED: { Address addr = reinterpret_cast
(event->code_start); Code* code = Code::GetCodeFromTargetAddress(addr); LineInfo* lineinfo = GetLineInfo(addr); EmbeddedVector buffer; StringBuilder builder(buffer.start(), buffer.length()); builder.AddSubstring(event->name.str, static_cast(event->name.len)); // It's called UnboundScript in the API but it's a SharedFunctionInfo. SharedFunctionInfo* shared = event->script.IsEmpty() ? nullptr : *Utils::OpenHandle(*event->script); AddCode(builder.Finalize(), code, shared, lineinfo); break; } case v8::JitCodeEvent::CODE_MOVED: // Enabling the GDB JIT interface should disable code compaction. UNREACHABLE(); break; case v8::JitCodeEvent::CODE_REMOVED: // Do nothing. Instead, adding code causes eviction of any entry whose // address range intersects the address range of the added code. break; case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO: { LineInfo* line_info = reinterpret_cast(event->user_data); line_info->SetPosition(static_cast(event->line_info.offset), static_cast(event->line_info.pos), event->line_info.position_type == v8::JitCodeEvent::STATEMENT_POSITION); break; } case v8::JitCodeEvent::CODE_START_LINE_INFO_RECORDING: { v8::JitCodeEvent* mutable_event = const_cast(event); mutable_event->user_data = new LineInfo(); break; } case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: { LineInfo* line_info = reinterpret_cast(event->user_data); PutLineInfo(reinterpret_cast
(event->code_start), line_info); break; } } } #endif } // namespace GDBJITInterface } // namespace internal } // namespace v8