// Copyright 2016 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/eh-frame.h" #include <iomanip> #include <ostream> #if !defined(V8_TARGET_ARCH_X64) && !defined(V8_TARGET_ARCH_ARM) && \ !defined(V8_TARGET_ARCH_ARM64) // Placeholders for unsupported architectures. namespace v8 { namespace internal { const int EhFrameConstants::kCodeAlignmentFactor = 1; const int EhFrameConstants::kDataAlignmentFactor = 1; void EhFrameWriter::WriteReturnAddressRegisterCode() { UNIMPLEMENTED(); } void EhFrameWriter::WriteInitialStateInCie() { UNIMPLEMENTED(); } int EhFrameWriter::RegisterToDwarfCode(Register) { UNIMPLEMENTED(); return -1; } #ifdef ENABLE_DISASSEMBLER const char* EhFrameDisassembler::DwarfRegisterCodeToString(int) { UNIMPLEMENTED(); return nullptr; } #endif } // namespace internal } // namespace v8 #endif namespace v8 { namespace internal { STATIC_CONST_MEMBER_DEFINITION const int EhFrameConstants::kEhFrameTerminatorSize; STATIC_CONST_MEMBER_DEFINITION const int EhFrameConstants::kEhFrameHdrVersion; STATIC_CONST_MEMBER_DEFINITION const int EhFrameConstants::kEhFrameHdrSize; STATIC_CONST_MEMBER_DEFINITION const uint32_t EhFrameWriter::kInt32Placeholder; // static void EhFrameWriter::WriteEmptyEhFrame(std::ostream& stream) { // NOLINT stream.put(EhFrameConstants::kEhFrameHdrVersion); // .eh_frame pointer encoding specifier. stream.put(EhFrameConstants::kSData4 | EhFrameConstants::kPcRel); // Lookup table size encoding. stream.put(EhFrameConstants::kUData4); // Lookup table entries encoding. stream.put(EhFrameConstants::kSData4 | EhFrameConstants::kDataRel); // Dummy pointers and 0 entries in the lookup table. char dummy_data[EhFrameConstants::kEhFrameHdrSize - 4] = {0}; stream.write(&dummy_data[0], sizeof(dummy_data)); } EhFrameWriter::EhFrameWriter(Zone* zone) : cie_size_(0), last_pc_offset_(0), writer_state_(InternalState::kUndefined), base_register_(no_reg), base_offset_(0), eh_frame_buffer_(zone) {} void EhFrameWriter::Initialize() { DCHECK(writer_state_ == InternalState::kUndefined); eh_frame_buffer_.reserve(128); writer_state_ = InternalState::kInitialized; WriteCie(); WriteFdeHeader(); } void EhFrameWriter::WriteCie() { static const int kCIEIdentifier = 0; static const int kCIEVersion = 3; static const int kAugmentationDataSize = 2; static const byte kAugmentationString[] = {'z', 'L', 'R', 0}; // Placeholder for the size of the CIE. int size_offset = eh_frame_offset(); WriteInt32(kInt32Placeholder); // CIE identifier and version. int record_start_offset = eh_frame_offset(); WriteInt32(kCIEIdentifier); WriteByte(kCIEVersion); // Augmentation data contents descriptor: LSDA and FDE encoding. WriteBytes(&kAugmentationString[0], sizeof(kAugmentationString)); // Alignment factors. WriteSLeb128(EhFrameConstants::kCodeAlignmentFactor); WriteSLeb128(EhFrameConstants::kDataAlignmentFactor); WriteReturnAddressRegisterCode(); // Augmentation data. WriteULeb128(kAugmentationDataSize); // No language-specific data area (LSDA). WriteByte(EhFrameConstants::kOmit); // FDE pointers encoding. WriteByte(EhFrameConstants::kSData4 | EhFrameConstants::kPcRel); // Write directives to build the initial state of the unwinding table. DCHECK_EQ(eh_frame_offset() - size_offset, EhFrameConstants::kInitialStateOffsetInCie); WriteInitialStateInCie(); WritePaddingToAlignedSize(eh_frame_offset() - record_start_offset); int record_end_offset = eh_frame_offset(); int encoded_cie_size = record_end_offset - record_start_offset; cie_size_ = record_end_offset - size_offset; // Patch the size of the CIE now that we know it. PatchInt32(size_offset, encoded_cie_size); } void EhFrameWriter::WriteFdeHeader() { DCHECK_NE(cie_size_, 0); // Placeholder for size of the FDE. Will be filled in Finish(). DCHECK_EQ(eh_frame_offset(), fde_offset()); WriteInt32(kInt32Placeholder); // Backwards offset to the CIE. WriteInt32(cie_size_ + kInt32Size); // Placeholder for pointer to procedure. Will be filled in Finish(). DCHECK_EQ(eh_frame_offset(), GetProcedureAddressOffset()); WriteInt32(kInt32Placeholder); // Placeholder for size of the procedure. Will be filled in Finish(). DCHECK_EQ(eh_frame_offset(), GetProcedureSizeOffset()); WriteInt32(kInt32Placeholder); // No augmentation data. WriteByte(0); } void EhFrameWriter::WriteEhFrameHdr(int code_size) { DCHECK(writer_state_ == InternalState::kInitialized); // // In order to calculate offsets in the .eh_frame_hdr, we must know the layout // of the DSO generated by perf inject, which is assumed to be the following: // // | ... | | // +---------------+ <-- (F) --- | Larger offsets in file // | | ^ | // | Instructions | | .text v // | | v // +---------------+ <-- (E) --- // |///////////////| // |////Padding////| // |///////////////| // +---------------+ <-- (D) --- // | | ^ // | CIE | | // | | | // +---------------+ <-- (C) | // | | | .eh_frame // | FDE | | // | | | // +---------------+ | // | terminator | v // +---------------+ <-- (B) --- // | version | ^ // +---------------+ | // | encoding | | // | specifiers | | // +---------------+ <---(A) | .eh_frame_hdr // | offset to | | // | .eh_frame | | // +---------------+ | // | ... | ... // // (F) is aligned to a 16-byte boundary. // (D) is aligned to a 8-byte boundary. // (B) is aligned to a 4-byte boundary. // (C), (E) and (A) have no alignment requirements. // // The distance between (A) and (B) is 4 bytes. // // The size of the FDE is required to be a multiple of the pointer size, which // means that (B) will be naturally aligned to a 4-byte boundary on all the // architectures we support. // // Because (E) has no alignment requirements, there is padding between (E) and // (D). (F) is aligned at a 16-byte boundary, thus to a 8-byte one as well. // int eh_frame_size = eh_frame_offset(); WriteByte(EhFrameConstants::kEhFrameHdrVersion); // .eh_frame pointer encoding specifier. WriteByte(EhFrameConstants::kSData4 | EhFrameConstants::kPcRel); // Lookup table size encoding specifier. WriteByte(EhFrameConstants::kUData4); // Lookup table entries encoding specifier. WriteByte(EhFrameConstants::kSData4 | EhFrameConstants::kDataRel); // Pointer to .eh_frame, relative to this offset (A -> D in the diagram). WriteInt32(-(eh_frame_size + EhFrameConstants::kFdeVersionSize + EhFrameConstants::kFdeEncodingSpecifiersSize)); // Number of entries in the LUT, one for the only routine. WriteInt32(1); // Pointer to the start of the routine, relative to the beginning of the // .eh_frame_hdr (B -> F in the diagram). WriteInt32(-(RoundUp(code_size, 8) + eh_frame_size)); // Pointer to the start of the associated FDE, relative to the start of the // .eh_frame_hdr (B -> C in the diagram). WriteInt32(-(eh_frame_size - cie_size_)); DCHECK_EQ(eh_frame_offset() - eh_frame_size, EhFrameConstants::kEhFrameHdrSize); } void EhFrameWriter::WritePaddingToAlignedSize(int unpadded_size) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_GE(unpadded_size, 0); int padding_size = RoundUp(unpadded_size, kPointerSize) - unpadded_size; byte nop = static_cast<byte>(EhFrameConstants::DwarfOpcodes::kNop); static const byte kPadding[] = {nop, nop, nop, nop, nop, nop, nop, nop}; DCHECK_LE(padding_size, static_cast<int>(sizeof(kPadding))); WriteBytes(&kPadding[0], padding_size); } void EhFrameWriter::AdvanceLocation(int pc_offset) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_GE(pc_offset, last_pc_offset_); uint32_t delta = pc_offset - last_pc_offset_; DCHECK_EQ(delta % EhFrameConstants::kCodeAlignmentFactor, 0u); uint32_t factored_delta = delta / EhFrameConstants::kCodeAlignmentFactor; if (factored_delta <= EhFrameConstants::kLocationMask) { WriteByte((EhFrameConstants::kLocationTag << EhFrameConstants::kLocationMaskSize) | (factored_delta & EhFrameConstants::kLocationMask)); } else if (factored_delta <= kMaxUInt8) { WriteOpcode(EhFrameConstants::DwarfOpcodes::kAdvanceLoc1); WriteByte(factored_delta); } else if (factored_delta <= kMaxUInt16) { WriteOpcode(EhFrameConstants::DwarfOpcodes::kAdvanceLoc2); WriteInt16(factored_delta); } else { WriteOpcode(EhFrameConstants::DwarfOpcodes::kAdvanceLoc4); WriteInt32(factored_delta); } last_pc_offset_ = pc_offset; } void EhFrameWriter::SetBaseAddressOffset(int base_offset) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_GE(base_offset, 0); WriteOpcode(EhFrameConstants::DwarfOpcodes::kDefCfaOffset); WriteULeb128(base_offset); base_offset_ = base_offset; } void EhFrameWriter::SetBaseAddressRegister(Register base_register) { DCHECK(writer_state_ == InternalState::kInitialized); int code = RegisterToDwarfCode(base_register); WriteOpcode(EhFrameConstants::DwarfOpcodes::kDefCfaRegister); WriteULeb128(code); base_register_ = base_register; } void EhFrameWriter::SetBaseAddressRegisterAndOffset(Register base_register, int base_offset) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_GE(base_offset, 0); int code = RegisterToDwarfCode(base_register); WriteOpcode(EhFrameConstants::DwarfOpcodes::kDefCfa); WriteULeb128(code); WriteULeb128(base_offset); base_offset_ = base_offset; base_register_ = base_register; } void EhFrameWriter::RecordRegisterSavedToStack(int register_code, int offset) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_EQ(offset % EhFrameConstants::kDataAlignmentFactor, 0); int factored_offset = offset / EhFrameConstants::kDataAlignmentFactor; if (factored_offset >= 0) { DCHECK_LE(register_code, EhFrameConstants::kSavedRegisterMask); WriteByte((EhFrameConstants::kSavedRegisterTag << EhFrameConstants::kSavedRegisterMaskSize) | (register_code & EhFrameConstants::kSavedRegisterMask)); WriteULeb128(factored_offset); } else { WriteOpcode(EhFrameConstants::DwarfOpcodes::kOffsetExtendedSf); WriteULeb128(register_code); WriteSLeb128(factored_offset); } } void EhFrameWriter::RecordRegisterNotModified(Register name) { DCHECK(writer_state_ == InternalState::kInitialized); WriteOpcode(EhFrameConstants::DwarfOpcodes::kSameValue); WriteULeb128(RegisterToDwarfCode(name)); } void EhFrameWriter::RecordRegisterFollowsInitialRule(Register name) { DCHECK(writer_state_ == InternalState::kInitialized); int code = RegisterToDwarfCode(name); DCHECK_LE(code, EhFrameConstants::kFollowInitialRuleMask); WriteByte((EhFrameConstants::kFollowInitialRuleTag << EhFrameConstants::kFollowInitialRuleMaskSize) | (code & EhFrameConstants::kFollowInitialRuleMask)); } void EhFrameWriter::Finish(int code_size) { DCHECK(writer_state_ == InternalState::kInitialized); DCHECK_GE(eh_frame_offset(), cie_size_); DCHECK_GE(eh_frame_offset(), fde_offset() + kInt32Size); WritePaddingToAlignedSize(eh_frame_offset() - fde_offset() - kInt32Size); // Write the size of the FDE now that we know it. // The encoded size does not include the size field itself. int encoded_fde_size = eh_frame_offset() - fde_offset() - kInt32Size; PatchInt32(fde_offset(), encoded_fde_size); // Write size and offset to procedure. PatchInt32(GetProcedureAddressOffset(), -(RoundUp(code_size, 8) + GetProcedureAddressOffset())); PatchInt32(GetProcedureSizeOffset(), code_size); // Terminate the .eh_frame. static const byte kTerminator[EhFrameConstants::kEhFrameTerminatorSize] = {0}; WriteBytes(&kTerminator[0], EhFrameConstants::kEhFrameTerminatorSize); WriteEhFrameHdr(code_size); writer_state_ = InternalState::kFinalized; } void EhFrameWriter::GetEhFrame(CodeDesc* desc) { DCHECK(writer_state_ == InternalState::kFinalized); desc->unwinding_info_size = static_cast<int>(eh_frame_buffer_.size()); desc->unwinding_info = eh_frame_buffer_.data(); } void EhFrameWriter::WriteULeb128(uint32_t value) { do { byte chunk = value & 0x7f; value >>= 7; if (value != 0) chunk |= 0x80; WriteByte(chunk); } while (value != 0); } void EhFrameWriter::WriteSLeb128(int32_t value) { static const int kSignBitMask = 0x40; bool done; do { byte chunk = value & 0x7f; value >>= 7; done = ((value == 0) && ((chunk & kSignBitMask) == 0)) || ((value == -1) && ((chunk & kSignBitMask) != 0)); if (!done) chunk |= 0x80; WriteByte(chunk); } while (!done); } uint32_t EhFrameIterator::GetNextULeb128() { int size = 0; uint32_t result = DecodeULeb128(next_, &size); DCHECK_LE(next_ + size, end_); next_ += size; return result; } int32_t EhFrameIterator::GetNextSLeb128() { int size = 0; int32_t result = DecodeSLeb128(next_, &size); DCHECK_LE(next_ + size, end_); next_ += size; return result; } // static uint32_t EhFrameIterator::DecodeULeb128(const byte* encoded, int* encoded_size) { const byte* current = encoded; uint32_t result = 0; int shift = 0; do { DCHECK_LT(shift, 8 * static_cast<int>(sizeof(result))); result |= (*current & 0x7f) << shift; shift += 7; } while (*current++ >= 128); DCHECK_NOT_NULL(encoded_size); *encoded_size = static_cast<int>(current - encoded); return result; } // static int32_t EhFrameIterator::DecodeSLeb128(const byte* encoded, int* encoded_size) { static const byte kSignBitMask = 0x40; const byte* current = encoded; int32_t result = 0; int shift = 0; byte chunk; do { chunk = *current++; DCHECK_LT(shift, 8 * static_cast<int>(sizeof(result))); result |= (chunk & 0x7f) << shift; shift += 7; } while (chunk >= 128); // Sign extend the result if the last chunk has the sign bit set. if (chunk & kSignBitMask) result |= (~0ull) << shift; DCHECK_NOT_NULL(encoded_size); *encoded_size = static_cast<int>(current - encoded); return result; } #ifdef ENABLE_DISASSEMBLER namespace { class StreamModifiersScope final { public: explicit StreamModifiersScope(std::ostream* stream) : stream_(stream), flags_(stream->flags()) {} ~StreamModifiersScope() { stream_->flags(flags_); } private: std::ostream* stream_; std::ios::fmtflags flags_; }; } // namespace // static void EhFrameDisassembler::DumpDwarfDirectives(std::ostream& stream, // NOLINT const byte* start, const byte* end) { StreamModifiersScope modifiers_scope(&stream); EhFrameIterator eh_frame_iterator(start, end); uint32_t offset_in_procedure = 0; while (!eh_frame_iterator.Done()) { stream << eh_frame_iterator.current_address() << " "; byte bytecode = eh_frame_iterator.GetNextByte(); if (((bytecode >> EhFrameConstants::kLocationMaskSize) & 0xff) == EhFrameConstants::kLocationTag) { int value = (bytecode & EhFrameConstants::kLocationMask) * EhFrameConstants::kCodeAlignmentFactor; offset_in_procedure += value; stream << "| pc_offset=" << offset_in_procedure << " (delta=" << value << ")\n"; continue; } if (((bytecode >> EhFrameConstants::kSavedRegisterMaskSize) & 0xff) == EhFrameConstants::kSavedRegisterTag) { int32_t decoded_offset = eh_frame_iterator.GetNextULeb128(); stream << "| " << DwarfRegisterCodeToString( bytecode & EhFrameConstants::kLocationMask) << " saved at base" << std::showpos << decoded_offset * EhFrameConstants::kDataAlignmentFactor << std::noshowpos << '\n'; continue; } if (((bytecode >> EhFrameConstants::kFollowInitialRuleMaskSize) & 0xff) == EhFrameConstants::kFollowInitialRuleTag) { stream << "| " << DwarfRegisterCodeToString( bytecode & EhFrameConstants::kLocationMask) << " follows rule in CIE\n"; continue; } switch (static_cast<EhFrameConstants::DwarfOpcodes>(bytecode)) { case EhFrameConstants::DwarfOpcodes::kOffsetExtendedSf: { stream << "| " << DwarfRegisterCodeToString(eh_frame_iterator.GetNextULeb128()); int32_t decoded_offset = eh_frame_iterator.GetNextSLeb128(); stream << " saved at base" << std::showpos << decoded_offset * EhFrameConstants::kDataAlignmentFactor << std::noshowpos << '\n'; break; } case EhFrameConstants::DwarfOpcodes::kAdvanceLoc1: { int value = eh_frame_iterator.GetNextByte() * EhFrameConstants::kCodeAlignmentFactor; offset_in_procedure += value; stream << "| pc_offset=" << offset_in_procedure << " (delta=" << value << ")\n"; break; } case EhFrameConstants::DwarfOpcodes::kAdvanceLoc2: { int value = eh_frame_iterator.GetNextUInt16() * EhFrameConstants::kCodeAlignmentFactor; offset_in_procedure += value; stream << "| pc_offset=" << offset_in_procedure << " (delta=" << value << ")\n"; break; } case EhFrameConstants::DwarfOpcodes::kAdvanceLoc4: { int value = eh_frame_iterator.GetNextUInt32() * EhFrameConstants::kCodeAlignmentFactor; offset_in_procedure += value; stream << "| pc_offset=" << offset_in_procedure << " (delta=" << value << ")\n"; break; } case EhFrameConstants::DwarfOpcodes::kDefCfa: { uint32_t base_register = eh_frame_iterator.GetNextULeb128(); uint32_t base_offset = eh_frame_iterator.GetNextULeb128(); stream << "| base_register=" << DwarfRegisterCodeToString(base_register) << ", base_offset=" << base_offset << '\n'; break; } case EhFrameConstants::DwarfOpcodes::kDefCfaOffset: { stream << "| base_offset=" << eh_frame_iterator.GetNextULeb128() << '\n'; break; } case EhFrameConstants::DwarfOpcodes::kDefCfaRegister: { stream << "| base_register=" << DwarfRegisterCodeToString(eh_frame_iterator.GetNextULeb128()) << '\n'; break; } case EhFrameConstants::DwarfOpcodes::kSameValue: { stream << "| " << DwarfRegisterCodeToString(eh_frame_iterator.GetNextULeb128()) << " not modified from previous frame\n"; break; } case EhFrameConstants::DwarfOpcodes::kNop: stream << "| nop\n"; break; default: UNREACHABLE(); return; } } } void EhFrameDisassembler::DisassembleToStream(std::ostream& stream) { // NOLINT // The encoded CIE size does not include the size field itself. const int cie_size = ReadUnalignedUInt32(start_) + kInt32Size; const int fde_offset = cie_size; const byte* cie_directives_start = start_ + EhFrameConstants::kInitialStateOffsetInCie; const byte* cie_directives_end = start_ + cie_size; DCHECK_LE(cie_directives_start, cie_directives_end); stream << reinterpret_cast<const void*>(start_) << " .eh_frame: CIE\n"; DumpDwarfDirectives(stream, cie_directives_start, cie_directives_end); const byte* procedure_offset_address = start_ + fde_offset + EhFrameConstants::kProcedureAddressOffsetInFde; int32_t procedure_offset = ReadUnalignedValue<int32_t>(procedure_offset_address); const byte* procedure_size_address = start_ + fde_offset + EhFrameConstants::kProcedureSizeOffsetInFde; uint32_t procedure_size = ReadUnalignedUInt32(procedure_size_address); const byte* fde_start = start_ + fde_offset; stream << reinterpret_cast<const void*>(fde_start) << " .eh_frame: FDE\n" << reinterpret_cast<const void*>(procedure_offset_address) << " | procedure_offset=" << procedure_offset << '\n' << reinterpret_cast<const void*>(procedure_size_address) << " | procedure_size=" << procedure_size << '\n'; const int fde_directives_offset = fde_offset + 4 * kInt32Size + 1; const byte* fde_directives_start = start_ + fde_directives_offset; const byte* fde_directives_end = end_ - EhFrameConstants::kEhFrameHdrSize - EhFrameConstants::kEhFrameTerminatorSize; DCHECK_LE(fde_directives_start, fde_directives_end); DumpDwarfDirectives(stream, fde_directives_start, fde_directives_end); const byte* fde_terminator_start = fde_directives_end; stream << reinterpret_cast<const void*>(fde_terminator_start) << " .eh_frame: terminator\n"; const byte* eh_frame_hdr_start = fde_terminator_start + EhFrameConstants::kEhFrameTerminatorSize; stream << reinterpret_cast<const void*>(eh_frame_hdr_start) << " .eh_frame_hdr\n"; } #endif } // namespace internal } // namespace v8