From b413f0ebe1a5dde016bfb94bb80bf872ebc24372 Mon Sep 17 00:00:00 2001 From: ssanfilippo Date: Wed, 13 Jul 2016 03:16:00 -0700 Subject: [PATCH] Reland Implement .eh_frame writer and disassembler. Original commit message: Also, CodeGenerator::MakeCodeEpilogue now accepts an optional pointer to a EhFrameWriter and will attach unwinding information to the code object when passed one. Reason for reverting: The STATIC_CONST_MEMBER_DEFINITION in eh-frame-writer-unittest.cc causes a compiler error on V8 Win64 - clang buildbot. Removing that bit. BUG=v8:4899 LOG=N Review-Url: https://codereview.chromium.org/2023503002 Cr-Commit-Position: refs/heads/master@{#37707} --- BUILD.gn | 3 + src/arm/eh-frame-arm.cc | 67 ++ src/arm64/eh-frame-arm64.cc | 72 ++ src/codegen.cc | 4 +- src/codegen.h | 3 +- src/compiler/code-generator.cc | 2 +- src/crankshaft/lithium.cc | 3 +- src/eh-frame.cc | 696 ++++++++++++++++--- src/eh-frame.h | 294 +++++++- src/full-codegen/full-codegen.cc | 2 +- src/objects.cc | 9 + src/perf-jit.cc | 17 +- src/v8.gyp | 3 + src/x64/eh-frame-x64.cc | 66 ++ test/cctest/cctest.gyp | 1 - test/cctest/test-eh-frame-hdr.cc | 100 --- test/unittests/eh-frame-iterator-unittest.cc | 61 ++ test/unittests/eh-frame-writer-unittest.cc | 464 +++++++++++++ test/unittests/unittests.gyp | 2 + 19 files changed, 1654 insertions(+), 215 deletions(-) create mode 100644 src/arm/eh-frame-arm.cc create mode 100644 src/arm64/eh-frame-arm64.cc create mode 100644 src/x64/eh-frame-x64.cc delete mode 100644 test/cctest/test-eh-frame-hdr.cc create mode 100644 test/unittests/eh-frame-iterator-unittest.cc create mode 100644 test/unittests/eh-frame-writer-unittest.cc diff --git a/BUILD.gn b/BUILD.gn index 02c25d301f..c6725478ee 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1635,6 +1635,7 @@ v8_source_set("v8_base") { "src/x64/cpu-x64.cc", "src/x64/deoptimizer-x64.cc", "src/x64/disasm-x64.cc", + "src/x64/eh-frame-x64.cc", "src/x64/frames-x64.cc", "src/x64/frames-x64.h", "src/x64/interface-descriptors-x64.cc", @@ -1656,6 +1657,7 @@ v8_source_set("v8_base") { "src/arm/cpu-arm.cc", "src/arm/deoptimizer-arm.cc", "src/arm/disasm-arm.cc", + "src/arm/eh-frame-arm.cc", "src/arm/frames-arm.cc", "src/arm/frames-arm.h", "src/arm/interface-descriptors-arm.cc", @@ -1702,6 +1704,7 @@ v8_source_set("v8_base") { "src/arm64/deoptimizer-arm64.cc", "src/arm64/disasm-arm64.cc", "src/arm64/disasm-arm64.h", + "src/arm64/eh-frame-arm64.cc", "src/arm64/frames-arm64.cc", "src/arm64/frames-arm64.h", "src/arm64/instructions-arm64.cc", diff --git a/src/arm/eh-frame-arm.cc b/src/arm/eh-frame-arm.cc new file mode 100644 index 0000000000..2ed49751bd --- /dev/null +++ b/src/arm/eh-frame-arm.cc @@ -0,0 +1,67 @@ +// 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" + +namespace v8 { +namespace internal { + +static const int kR0DwarfCode = 0; +static const int kFpDwarfCode = 11; +static const int kSpDwarfCode = 13; +static const int kLrDwarfCode = 14; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kCodeAlignmentFactor = 4; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kDataAlignmentFactor = -4; + +void EhFrameWriter::WriteReturnAddressRegisterCode() { + WriteULeb128(kLrDwarfCode); +} + +void EhFrameWriter::WriteInitialStateInCie() { + SetBaseAddressRegisterAndOffset(fp, 0); + RecordRegisterNotModified(lr); +} + +// static +int EhFrameWriter::RegisterToDwarfCode(Register name) { + switch (name.code()) { + case Register::kCode_fp: + return kFpDwarfCode; + case Register::kCode_sp: + return kSpDwarfCode; + case Register::kCode_lr: + return kLrDwarfCode; + case Register::kCode_r0: + return kR0DwarfCode; + default: + UNIMPLEMENTED(); + return -1; + } +} + +#ifdef ENABLE_DISASSEMBLER + +// static +const char* EhFrameDisassembler::DwarfRegisterCodeToString(int code) { + switch (code) { + case kFpDwarfCode: + return "fp"; + case kSpDwarfCode: + return "sp"; + case kLrDwarfCode: + return "lr"; + default: + UNIMPLEMENTED(); + return nullptr; + } +} + +#endif + +} // namespace internal +} // namespace v8 diff --git a/src/arm64/eh-frame-arm64.cc b/src/arm64/eh-frame-arm64.cc new file mode 100644 index 0000000000..5825bccfe0 --- /dev/null +++ b/src/arm64/eh-frame-arm64.cc @@ -0,0 +1,72 @@ +// 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" + +namespace v8 { +namespace internal { + +static const int kX0DwarfCode = 0; +static const int kJsSpDwarfCode = 28; +static const int kFpDwarfCode = 29; +static const int kLrDwarfCode = 30; +static const int kCSpDwarfCode = 31; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kCodeAlignmentFactor = 4; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kDataAlignmentFactor = -8; + +void EhFrameWriter::WriteReturnAddressRegisterCode() { + WriteULeb128(kLrDwarfCode); +} + +void EhFrameWriter::WriteInitialStateInCie() { + SetBaseAddressRegisterAndOffset(x29, 0); + RecordRegisterNotModified(x30); +} + +// static +int EhFrameWriter::RegisterToDwarfCode(Register name) { + switch (name.code()) { + case Register::kCode_x28: + return kJsSpDwarfCode; + case Register::kCode_x29: + return kFpDwarfCode; + case Register::kCode_x30: + return kLrDwarfCode; + case Register::kCode_x31: + return kCSpDwarfCode; + case Register::kCode_x0: + return kX0DwarfCode; + default: + UNIMPLEMENTED(); + return -1; + } +} + +#ifdef ENABLE_DISASSEMBLER + +// static +const char* EhFrameDisassembler::DwarfRegisterCodeToString(int code) { + switch (code) { + case kFpDwarfCode: + return "fp"; + case kLrDwarfCode: + return "lr"; + case kJsSpDwarfCode: + return "jssp"; + case kCSpDwarfCode: + return "csp"; // This could be zr as well + default: + UNIMPLEMENTED(); + return nullptr; + } +} + +#endif + +} // namespace internal +} // namespace v8 diff --git a/src/codegen.cc b/src/codegen.cc index 4597ae2766..d4c1e438cb 100644 --- a/src/codegen.cc +++ b/src/codegen.cc @@ -11,6 +11,7 @@ #include "src/bootstrapper.h" #include "src/compiler.h" #include "src/debug/debug.h" +#include "src/eh-frame.h" #include "src/parsing/parser.h" #include "src/runtime/runtime.h" @@ -117,8 +118,8 @@ void CodeGenerator::MakeCodePrologue(CompilationInfo* info, const char* kind) { #endif // DEBUG } - Handle CodeGenerator::MakeCodeEpilogue(MacroAssembler* masm, + EhFrameWriter* eh_frame_writer, CompilationInfo* info) { Isolate* isolate = info->isolate(); @@ -129,6 +130,7 @@ Handle CodeGenerator::MakeCodeEpilogue(MacroAssembler* masm, Code::ExtractKindFromFlags(flags) == Code::OPTIMIZED_FUNCTION || info->IsStub(); masm->GetCode(&desc); + if (eh_frame_writer) eh_frame_writer->GetEhFrame(&desc); Handle code = isolate->factory()->NewCode(desc, flags, masm->CodeObject(), false, is_crankshafted, diff --git a/src/codegen.h b/src/codegen.h index 82962ad802..c49fd8825c 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -69,7 +69,7 @@ namespace internal { class CompilationInfo; - +class EhFrameWriter; class CodeGenerator { public: @@ -78,6 +78,7 @@ class CodeGenerator { // Allocate and install the code. static Handle MakeCodeEpilogue(MacroAssembler* masm, + EhFrameWriter* unwinding, CompilationInfo* info); // Print the code after compiling it. diff --git a/src/compiler/code-generator.cc b/src/compiler/code-generator.cc index 9c858c67a8..4c20fc56ba 100644 --- a/src/compiler/code-generator.cc +++ b/src/compiler/code-generator.cc @@ -204,7 +204,7 @@ Handle CodeGenerator::GenerateCode() { safepoints()->Emit(masm(), frame()->GetTotalFrameSlotCount()); Handle result = - v8::internal::CodeGenerator::MakeCodeEpilogue(masm(), info); + v8::internal::CodeGenerator::MakeCodeEpilogue(masm(), nullptr, info); result->set_is_turbofanned(true); result->set_stack_slots(frame()->GetTotalFrameSlotCount()); result->set_safepoint_table_offset(safepoints()->GetCodeOffset()); diff --git a/src/crankshaft/lithium.cc b/src/crankshaft/lithium.cc index 0bb4ed98d3..6f226da280 100644 --- a/src/crankshaft/lithium.cc +++ b/src/crankshaft/lithium.cc @@ -457,7 +457,8 @@ Handle LChunk::Codegen() { if (generator.GenerateCode()) { generator.CheckEnvironmentUsage(); CodeGenerator::MakeCodePrologue(info(), "optimized"); - Handle code = CodeGenerator::MakeCodeEpilogue(&assembler, info()); + Handle code = + CodeGenerator::MakeCodeEpilogue(&assembler, nullptr, info()); generator.FinishCode(code); CommitDependencies(code); generator.source_position_table_builder()->EndJitLogging( diff --git a/src/eh-frame.cc b/src/eh-frame.cc index af85e0b8d5..cceed6620a 100644 --- a/src/eh-frame.cc +++ b/src/eh-frame.cc @@ -3,94 +3,632 @@ // found in the LICENSE file. #include "src/eh-frame.h" -#include "src/objects-inl.h" -#include "src/objects.h" + +#include +#include + +#if !defined(V8_TARGET_ARCH_X64) && !defined(V8_TARGET_ARCH_ARM) && \ + !defined(V8_TARGET_ARCH_ARM64) + +// Placeholders for unsupported architectures. namespace v8 { namespace internal { -static const int DW_EH_PE_pcrel = 0x10; -static const int DW_EH_PE_datarel = 0x30; -static const int DW_EH_PE_udata4 = 0x03; -static const int DW_EH_PE_sdata4 = 0x0b; +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kCodeAlignmentFactor = 1; -const int EhFrameHdr::kCIESize = 0; +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kDataAlignmentFactor = 1; -static const int kVersionSize = 1; -static const int kEncodingSpecifiersSize = 3; +void EhFrameWriter::WriteReturnAddressRegisterCode() { UNIMPLEMENTED(); } -// -// 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 | | -// | | v -// +---------------+ <-- (B) --- -// | version | ^ -// +---------------+ | -// | encoding | | -// | specifiers | | -// +---------------+ <---(A) | .eh_frame_hdr -// | offset to | | -// | .eh_frame | | -// +---------------+ | -// | ... | ... -// -// (F) is aligned at a 16-byte boundary. -// (D) is aligned at a 8-byte boundary. -// (B) is aligned at a 4-byte boundary. -// (E), (C) and (A) have no alignment requirements. -// -// The distance between (A) and (B) is 4 bytes. -// -// The size of the .eh_frame 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. -// -EhFrameHdr::EhFrameHdr(Code* code) { - int code_size = code->is_crankshafted() ? code->safepoint_table_offset() - : code->instruction_size(); - version_ = 1; - eh_frame_ptr_encoding_ = DW_EH_PE_sdata4 | DW_EH_PE_pcrel; - lut_size_encoding_ = DW_EH_PE_udata4; - lut_entries_encoding_ = DW_EH_PE_sdata4 | DW_EH_PE_datarel; +void EhFrameWriter::WriteInitialStateInCie() { UNIMPLEMENTED(); } - // .eh_frame pointer and LUT - if (code->has_unwinding_info()) { - DCHECK_GE(code->unwinding_info_size(), EhFrameHdr::kRecordSize); - int eh_frame_size = code->unwinding_info_size() - EhFrameHdr::kRecordSize; - - offset_to_eh_frame_ = - -(eh_frame_size + kVersionSize + kEncodingSpecifiersSize); // A -> D - lut_entries_number_ = 1; - offset_to_procedure_ = -(RoundUp(code_size, 8) + eh_frame_size); // B -> F - offset_to_fde_ = -(eh_frame_size - kCIESize); // B -> C - } else { - // Create a dummy table - offset_to_eh_frame_ = 0; - lut_entries_number_ = 0; - offset_to_procedure_ = 0; - offset_to_fde_ = 0; - } +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(); + + // Pad with nops to the next multiple of 8 bytes. + WritePaddingTo8ByteAlignment(); + + 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) is aligned to an addressing unit size boundary. + // (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::WritePaddingTo8ByteAlignment() { + DCHECK(writer_state_ == InternalState::kInitialized); + + int unpadded_size = eh_frame_offset(); + int padded_size = RoundUp(unpadded_size, 8); + int padding_size = padded_size - unpadded_size; + + byte nop = static_cast(EhFrameConstants::DwarfOpcodes::kNop); + static const byte kPadding[] = {nop, nop, nop, nop, nop, nop, nop, nop}; + DCHECK_LE(padding_size, static_cast(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, 0); + 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_); + + WritePaddingTo8ByteAlignment(); + + // 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(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(sizeof(result))); + result |= (*current & 0x7f) << shift; + shift += 7; + } while (*current++ >= 128); + + DCHECK_NOT_NULL(encoded_size); + *encoded_size = static_cast(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(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(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) { + uint32_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(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(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(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(fde_start) << " .eh_frame: FDE\n" + << reinterpret_cast(procedure_offset_address) + << " | procedure_offset=" << procedure_offset << '\n' + << reinterpret_cast(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(fde_terminator_start) + << " .eh_frame: terminator\n"; + + const byte* eh_frame_hdr_start = + fde_terminator_start + EhFrameConstants::kEhFrameTerminatorSize; + stream << reinterpret_cast(eh_frame_hdr_start) + << " .eh_frame_hdr\n"; +} + +#endif + } // namespace internal } // namespace v8 diff --git a/src/eh-frame.h b/src/eh-frame.h index 75781acb50..00de2951d3 100644 --- a/src/eh-frame.h +++ b/src/eh-frame.h @@ -5,36 +5,292 @@ #ifndef V8_EH_FRAME_H_ #define V8_EH_FRAME_H_ -#include +#include "src/macro-assembler.h" namespace v8 { namespace internal { -class Code; - -class EhFrameHdr final { +class EhFrameConstants final { public: - static const int kRecordSize = 20; - static const int kCIESize; + enum class DwarfOpcodes : byte { + kNop = 0x00, + kAdvanceLoc1 = 0x02, + kAdvanceLoc2 = 0x03, + kAdvanceLoc4 = 0x04, + kSameValue = 0x08, + kDefCfa = 0x0c, + kDefCfaRegister = 0x0d, + kDefCfaOffset = 0x0e, + kOffsetExtendedSf = 0x11, + }; - explicit EhFrameHdr(Code* code); + enum DwarfEncodingSpecifiers : byte { + kUData4 = 0x03, + kSData4 = 0x0b, + kPcRel = 0x10, + kDataRel = 0x30, + kOmit = 0xff, + }; - int32_t offset_to_eh_frame() const { return offset_to_eh_frame_; } - uint32_t lut_entries_number() const { return lut_entries_number_; } - int32_t offset_to_procedure() const { return offset_to_procedure_; } - int32_t offset_to_fde() const { return offset_to_fde_; } + static const int kLocationTag = 1; + static const int kLocationMask = 0x3f; + static const int kLocationMaskSize = 6; + + static const int kSavedRegisterTag = 2; + static const int kSavedRegisterMask = 0x3f; + static const int kSavedRegisterMaskSize = 6; + + static const int kFollowInitialRuleTag = 3; + static const int kFollowInitialRuleMask = 0x3f; + static const int kFollowInitialRuleMaskSize = 6; + + static const int kProcedureAddressOffsetInFde = 2 * kInt32Size; + static const int kProcedureSizeOffsetInFde = 3 * kInt32Size; + + static const int kInitialStateOffsetInCie = 19; + static const int kEhFrameTerminatorSize = 4; + + // Defined in eh-writer-.cc + static const int kCodeAlignmentFactor; + static const int kDataAlignmentFactor; + + static const int kFdeVersionSize = 1; + static const int kFdeEncodingSpecifiersSize = 3; + + static const int kEhFrameHdrVersion = 1; + static const int kEhFrameHdrSize = 20; +}; + +class EhFrameWriter { + public: + explicit EhFrameWriter(Zone* zone); + + // The empty frame is a hack to trigger fp-based unwinding in Linux perf + // compiled with libunwind support when processing DWARF-based call graphs. + // + // It is effectively a valid eh_frame_hdr with an empty look up table. + // + static void WriteEmptyEhFrame(std::ostream& stream); // NOLINT + + // Write the CIE and FDE header. Call it before any other method. + void Initialize(); + + void AdvanceLocation(int pc_offset); + + // The is the one to which all s in SaveRegisterToStack + // directives are relative. It is given by + . + // + // The must be positive or 0. + // + void SetBaseAddressRegister(Register base_register); + void SetBaseAddressOffset(int base_offset); + void IncreaseBaseAddressOffset(int base_delta) { + SetBaseAddressOffset(base_offset_ + base_delta); + } + void SetBaseAddressRegisterAndOffset(Register base_register, int base_offset); + + // Register saved at location + . + // The must be a multiple of EhFrameConstants::kDataAlignment. + void RecordRegisterSavedToStack(Register name, int offset) { + RecordRegisterSavedToStack(RegisterToDwarfCode(name), offset); + } + + // The register has not been modified from the previous frame. + void RecordRegisterNotModified(Register name); + + // The register follows the rule defined in the CIE. + void RecordRegisterFollowsInitialRule(Register name); + + void Finish(int code_size); + + // Remember to call Finish() before GetEhFrame(). + // + // The EhFrameWriter instance owns the buffer pointed by + // CodeDesc::unwinding_info, and must outlive any use of the CodeDesc. + // + void GetEhFrame(CodeDesc* desc); + + int last_pc_offset() const { return last_pc_offset_; } + Register base_register() const { return base_register_; } + int base_offset() const { return base_offset_; } private: - uint8_t version_; - uint8_t eh_frame_ptr_encoding_; - uint8_t lut_size_encoding_; - uint8_t lut_entries_encoding_; - int32_t offset_to_eh_frame_; - uint32_t lut_entries_number_; - int32_t offset_to_procedure_; - int32_t offset_to_fde_; + enum class InternalState { kUndefined, kInitialized, kFinalized }; + + static const uint32_t kInt32Placeholder = 0xdeadc0de; + + void WriteSLeb128(int32_t value); + void WriteULeb128(uint32_t value); + + void WriteByte(byte value) { eh_frame_buffer_.push_back(value); } + void WriteOpcode(EhFrameConstants::DwarfOpcodes opcode) { + WriteByte(static_cast(opcode)); + } + void WriteBytes(const byte* start, int size) { + eh_frame_buffer_.insert(eh_frame_buffer_.end(), start, start + size); + } + void WriteInt16(uint16_t value) { + WriteBytes(reinterpret_cast(&value), sizeof(value)); + } + void WriteInt32(uint32_t value) { + WriteBytes(reinterpret_cast(&value), sizeof(value)); + } + void PatchInt32(int base_offset, uint32_t value) { + DCHECK_EQ(ReadUnalignedUInt32(eh_frame_buffer_.data() + base_offset), + kInt32Placeholder); + DCHECK_LT(base_offset + kInt32Size, eh_frame_offset()); + WriteUnalignedUInt32(eh_frame_buffer_.data() + base_offset, value); + } + + // Write the common information entry, which includes encoding specifiers, + // alignment factors, the return address (pseudo) register code and the + // directives to construct the initial state of the unwinding table. + void WriteCie(); + + // Write the header of the function data entry, containing a pointer to the + // correspondent CIE and the position and size of the associated routine. + void WriteFdeHeader(); + + // Write the contents of the .eh_frame_hdr section, including encoding + // specifiers and the routine => FDE lookup table. + void WriteEhFrameHdr(int code_size); + + // Write nops to the buffer until the size reaches a multiple of 8 bytes. + void WritePaddingTo8ByteAlignment(); + + // Internal version that directly accepts a DWARF register code, needed for + // handling pseudo-registers on some platforms. + void RecordRegisterSavedToStack(int register_code, int offset); + + int GetProcedureAddressOffset() const { + return fde_offset() + EhFrameConstants::kProcedureAddressOffsetInFde; + } + + int GetProcedureSizeOffset() const { + return fde_offset() + EhFrameConstants::kProcedureSizeOffsetInFde; + } + + int eh_frame_offset() const { + return static_cast(eh_frame_buffer_.size()); + } + + int fde_offset() const { return cie_size_; } + + // Platform specific functions implemented in eh-frame-.cc + + static int RegisterToDwarfCode(Register name); + + // Write directives to build the initial state in the CIE. + void WriteInitialStateInCie(); + + // Write the return address (pseudo) register code. + void WriteReturnAddressRegisterCode(); + + int cie_size_; + int last_pc_offset_; + InternalState writer_state_; + Register base_register_; + int base_offset_; + ZoneVector eh_frame_buffer_; + + DISALLOW_COPY_AND_ASSIGN(EhFrameWriter); }; +class EhFrameIterator { + public: + EhFrameIterator(const byte* start, const byte* end) + : start_(start), next_(start), end_(end) { + DCHECK_LE(start, end); + } + + void SkipCie() { + DCHECK_EQ(next_, start_); + next_ += ReadUnalignedUInt32(next_) + kInt32Size; + } + + void SkipToFdeDirectives() { + SkipCie(); + // Skip the FDE header. + Skip(kDirectivesOffsetInFde); + } + + void Skip(int how_many) { + DCHECK_GE(how_many, 0); + next_ += how_many; + DCHECK_LE(next_, end_); + } + + uint32_t GetNextUInt32() { return GetNextValue(); } + uint16_t GetNextUInt16() { return GetNextValue(); } + byte GetNextByte() { return GetNextValue(); } + EhFrameConstants::DwarfOpcodes GetNextOpcode() { + return static_cast(GetNextByte()); + } + + uint32_t GetNextULeb128(); + int32_t GetNextSLeb128(); + + bool Done() const { + DCHECK_LE(next_, end_); + return next_ == end_; + } + + int GetCurrentOffset() const { + DCHECK_GE(next_, start_); + return static_cast(next_ - start_); + } + + int GetBufferSize() { return static_cast(end_ - start_); } + + const void* current_address() const { + return reinterpret_cast(next_); + } + + private: + static const int kDirectivesOffsetInFde = 4 * kInt32Size + 1; + + static uint32_t DecodeULeb128(const byte* encoded, int* encoded_size); + static int32_t DecodeSLeb128(const byte* encoded, int* encoded_size); + + template + T GetNextValue() { + T result; + DCHECK_LE(next_ + sizeof(result), end_); + result = ReadUnalignedValue(next_); + next_ += sizeof(result); + return result; + } + + const byte* start_; + const byte* next_; + const byte* end_; +}; + +#ifdef ENABLE_DISASSEMBLER + +class EhFrameDisassembler final { + public: + EhFrameDisassembler(const byte* start, const byte* end) + : start_(start), end_(end) { + DCHECK_LT(start, end); + } + + void DisassembleToStream(std::ostream& stream); // NOLINT + + private: + static void DumpDwarfDirectives(std::ostream& stream, // NOLINT + const byte* start, const byte* end); + + static const char* DwarfRegisterCodeToString(int code); + + const byte* start_; + const byte* end_; + + DISALLOW_COPY_AND_ASSIGN(EhFrameDisassembler); +}; + +#endif + } // namespace internal } // namespace v8 diff --git a/src/full-codegen/full-codegen.cc b/src/full-codegen/full-codegen.cc index f98c3096a6..f8ffc3408b 100644 --- a/src/full-codegen/full-codegen.cc +++ b/src/full-codegen/full-codegen.cc @@ -54,7 +54,7 @@ bool FullCodeGenerator::MakeCode(CompilationInfo* info) { } unsigned table_offset = cgen.EmitBackEdgeTable(); - Handle code = CodeGenerator::MakeCodeEpilogue(&masm, info); + Handle code = CodeGenerator::MakeCodeEpilogue(&masm, nullptr, info); cgen.PopulateDeoptimizationData(code); cgen.PopulateTypeFeedbackInfo(code); cgen.PopulateHandlerTable(code); diff --git a/src/objects.cc b/src/objects.cc index 18ebc29e2e..01c2e1a4c9 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -61,6 +61,7 @@ #ifdef ENABLE_DISASSEMBLER #include "src/disasm.h" #include "src/disassembler.h" +#include "src/eh-frame.h" #endif namespace v8 { @@ -14372,6 +14373,14 @@ void Code::Disassemble(const char* name, std::ostream& os) { // NOLINT it.rinfo()->Print(GetIsolate(), os); } os << "\n"; + + if (has_unwinding_info()) { + os << "UnwindingInfo (size = " << unwinding_info_size() << ")\n"; + EhFrameDisassembler eh_frame_disassembler(unwinding_info_start(), + unwinding_info_end()); + eh_frame_disassembler.DisassembleToStream(os); + os << "\n"; + } } #endif // ENABLE_DISASSEMBLER diff --git a/src/perf-jit.cc b/src/perf-jit.cc index 3ac179ec8f..b318a4e7c0 100644 --- a/src/perf-jit.cc +++ b/src/perf-jit.cc @@ -324,18 +324,16 @@ void PerfJitLogger::LogWriteDebugInfo(Code* code, SharedFunctionInfo* shared) { } void PerfJitLogger::LogWriteUnwindingInfo(Code* code) { - EhFrameHdr eh_frame_hdr(code); - PerfJitCodeUnwindingInfo unwinding_info_header; unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo; unwinding_info_header.time_stamp_ = GetTimestamp(); - unwinding_info_header.eh_frame_hdr_size_ = EhFrameHdr::kRecordSize; + unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize; if (code->has_unwinding_info()) { unwinding_info_header.unwinding_size_ = code->unwinding_info_size(); unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_; } else { - unwinding_info_header.unwinding_size_ = EhFrameHdr::kRecordSize; + unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize; unwinding_info_header.mapped_size_ = 0; } @@ -348,16 +346,13 @@ void PerfJitLogger::LogWriteUnwindingInfo(Code* code) { sizeof(unwinding_info_header)); if (code->has_unwinding_info()) { - // The last EhFrameHdr::kRecordSize bytes were a placeholder for the header. - // Discard them and write the actual eh_frame_hdr (below). - DCHECK_GE(code->unwinding_info_size(), EhFrameHdr::kRecordSize); LogWriteBytes(reinterpret_cast(code->unwinding_info_start()), - code->unwinding_info_size() - EhFrameHdr::kRecordSize); + code->unwinding_info_size()); + } else { + OFStream perf_output_stream(perf_output_handle_); + EhFrameWriter::WriteEmptyEhFrame(perf_output_stream); } - LogWriteBytes(reinterpret_cast(&eh_frame_hdr), - EhFrameHdr::kRecordSize); - char padding_bytes[] = "\0\0\0\0\0\0\0\0"; DCHECK_LT(padding_size, sizeof(padding_bytes)); LogWriteBytes(padding_bytes, padding_size); diff --git a/src/v8.gyp b/src/v8.gyp index e59af03cd4..ba224dd2ee 100644 --- a/src/v8.gyp +++ b/src/v8.gyp @@ -1231,6 +1231,7 @@ 'arm/macro-assembler-arm.h', 'arm/simulator-arm.cc', 'arm/simulator-arm.h', + 'arm/eh-frame-arm.cc', 'compiler/arm/code-generator-arm.cc', 'compiler/arm/instruction-codes-arm.h', 'compiler/arm/instruction-scheduler-arm.cc', @@ -1285,6 +1286,7 @@ 'arm64/simulator-arm64.h', 'arm64/utils-arm64.cc', 'arm64/utils-arm64.h', + 'arm64/eh-frame-arm64.cc', 'compiler/arm64/code-generator-arm64.cc', 'compiler/arm64/instruction-codes-arm64.h', 'compiler/arm64/instruction-scheduler-arm64.cc', @@ -1514,6 +1516,7 @@ 'compiler/x64/instruction-codes-x64.h', 'compiler/x64/instruction-scheduler-x64.cc', 'compiler/x64/instruction-selector-x64.cc', + 'x64/eh-frame-x64.cc', ], }], ['v8_target_arch=="ppc" or v8_target_arch=="ppc64"', { diff --git a/src/x64/eh-frame-x64.cc b/src/x64/eh-frame-x64.cc new file mode 100644 index 0000000000..ed27f09c03 --- /dev/null +++ b/src/x64/eh-frame-x64.cc @@ -0,0 +1,66 @@ +// 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" + +namespace v8 { +namespace internal { + +static const int kRaxDwarfCode = 0; +static const int kRbpDwarfCode = 6; +static const int kRspDwarfCode = 7; +static const int kRipDwarfCode = 16; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kCodeAlignmentFactor = 1; + +STATIC_CONST_MEMBER_DEFINITION const int + EhFrameConstants::kDataAlignmentFactor = -8; + +void EhFrameWriter::WriteReturnAddressRegisterCode() { + WriteULeb128(kRipDwarfCode); +} + +void EhFrameWriter::WriteInitialStateInCie() { + SetBaseAddressRegisterAndOffset(rsp, kPointerSize); + // x64 rip (r16) has no Register instance associated. + RecordRegisterSavedToStack(kRipDwarfCode, -kPointerSize); +} + +// static +int EhFrameWriter::RegisterToDwarfCode(Register name) { + switch (name.code()) { + case Register::kCode_rbp: + return kRbpDwarfCode; + case Register::kCode_rsp: + return kRspDwarfCode; + case Register::kCode_rax: + return kRaxDwarfCode; + default: + UNIMPLEMENTED(); + return -1; + } +} + +#ifdef ENABLE_DISASSEMBLER + +// static +const char* EhFrameDisassembler::DwarfRegisterCodeToString(int code) { + switch (code) { + case kRbpDwarfCode: + return "rbp"; + case kRspDwarfCode: + return "rsp"; + case kRipDwarfCode: + return "rip"; + default: + UNIMPLEMENTED(); + return nullptr; + } +} + +#endif + +} // namespace internal +} // namespace v8 diff --git a/test/cctest/cctest.gyp b/test/cctest/cctest.gyp index 0313d89c91..5dd6bdb6ec 100644 --- a/test/cctest/cctest.gyp +++ b/test/cctest/cctest.gyp @@ -138,7 +138,6 @@ 'test-double.cc', 'test-dtoa.cc', 'test-elements-kind.cc', - 'test-eh-frame-hdr.cc', 'test-fast-dtoa.cc', 'test-feedback-vector.cc', 'test-field-type-tracking.cc', diff --git a/test/cctest/test-eh-frame-hdr.cc b/test/cctest/test-eh-frame-hdr.cc deleted file mode 100644 index d9539c7d2c..0000000000 --- a/test/cctest/test-eh-frame-hdr.cc +++ /dev/null @@ -1,100 +0,0 @@ -// 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 "src/objects.h" -#include "test/cctest/cctest.h" - -using namespace v8::internal; - -TEST(EhFrameHdr) { - CcTest::InitializeVM(); - HandleScope handle_scope(CcTest::i_isolate()); - - // The content is not relevant in this test - byte buffer[10] = {0}; - byte unwinding_info[30 + EhFrameHdr::kRecordSize] = {0}; - - CodeDesc code_desc; - code_desc.buffer = &buffer[0]; - code_desc.buffer_size = sizeof(buffer); - code_desc.constant_pool_size = 0; - code_desc.instr_size = sizeof(buffer); - code_desc.reloc_size = 0; - code_desc.origin = nullptr; - code_desc.unwinding_info = &unwinding_info[0]; - code_desc.unwinding_info_size = sizeof(unwinding_info); - - Handle code = CcTest::i_isolate()->factory()->NewCode( - code_desc, 0, Handle::null()); - - EhFrameHdr eh_frame_hdr(*code); - CHECK_EQ(eh_frame_hdr.lut_entries_number(), 1); - - // - // Plugging some numbers in the DSO layout shown in eh-frame.cc: - // - // | ... | - // +---------------+ <-- (E) -------- - // | | ^ - // | Instructions | 10 bytes | .text - // | | v - // +---------------+ <--------------- - // |///////////////| - // |////Padding////| 6 bytes - // |///////////////| - // +---------------+ <---(D)--------- - // | | ^ - // | CIE | N bytes* | - // | | | - // +---------------+ <-- (C) | .eh_frame - // | | | - // | FDE | 30 - N bytes | - // | | v - // +---------------+ <-- (B) -------- - // | version | ^ - // +---------------+ 4 bytes | - // | encoding | | - // | specifiers | | - // +---------------+ <---(A) | .eh_frame_hdr - // | offset to | | - // | .eh_frame | | - // +---------------+ | - // | ... | ... - // - // (*) the size of the CIE is platform dependent. - // - CHECK_EQ(eh_frame_hdr.offset_to_eh_frame(), -(4 + 30)); // A -> D - CHECK_EQ(eh_frame_hdr.offset_to_procedure(), -(30 + 6 + 10)); // B -> E - CHECK_EQ(eh_frame_hdr.offset_to_fde(), - -(30 - EhFrameHdr::kCIESize)); // B -> C -} - -TEST(DummyEhFrameHdr) { - CcTest::InitializeVM(); - HandleScope handle_scope(CcTest::i_isolate()); - - byte buffer[10] = {0}; // The content is not relevant in this test - - CodeDesc code_desc; - code_desc.buffer = &buffer[0]; - code_desc.buffer_size = sizeof(buffer); - code_desc.constant_pool_size = 0; - code_desc.instr_size = sizeof(buffer); - code_desc.reloc_size = 0; - code_desc.origin = nullptr; - code_desc.unwinding_info = nullptr; - code_desc.unwinding_info_size = 0; - - Handle code = CcTest::i_isolate()->factory()->NewCode( - code_desc, 0, Handle::null()); - - EhFrameHdr eh_frame_hdr(*code); - // A dummy header has an empty LUT - CHECK_EQ(eh_frame_hdr.lut_entries_number(), 0); - // These values should be irrelevant, but check that they have been zeroed. - CHECK_EQ(eh_frame_hdr.offset_to_eh_frame(), 0); - CHECK_EQ(eh_frame_hdr.offset_to_procedure(), 0); - CHECK_EQ(eh_frame_hdr.offset_to_fde(), 0); -} diff --git a/test/unittests/eh-frame-iterator-unittest.cc b/test/unittests/eh-frame-iterator-unittest.cc new file mode 100644 index 0000000000..27485db67e --- /dev/null +++ b/test/unittests/eh-frame-iterator-unittest.cc @@ -0,0 +1,61 @@ +// 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 "testing/gtest/include/gtest/gtest.h" + +// Test enabled only on supported architectures. +#if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM) || \ + defined(V8_TARGET_ARCH_ARM64) + +using namespace v8::internal; + +namespace { + +class EhFrameIteratorTest : public testing::Test {}; + +} // namespace + +TEST_F(EhFrameIteratorTest, Values) { + // Assuming little endian. + static const byte kEncoded[] = {0xde, 0xc0, 0xad, 0xde, 0xef, 0xbe, 0xff}; + EhFrameIterator iterator(&kEncoded[0], &kEncoded[0] + sizeof(kEncoded)); + EXPECT_EQ(0xdeadc0de, iterator.GetNextUInt32()); + EXPECT_EQ(0xbeef, iterator.GetNextUInt16()); + EXPECT_EQ(0xff, iterator.GetNextByte()); + EXPECT_TRUE(iterator.Done()); +} + +TEST_F(EhFrameIteratorTest, Skip) { + static const byte kEncoded[] = {0xde, 0xad, 0xc0, 0xde}; + EhFrameIterator iterator(&kEncoded[0], &kEncoded[0] + sizeof(kEncoded)); + iterator.Skip(2); + EXPECT_EQ(2, iterator.GetCurrentOffset()); + EXPECT_EQ(0xc0, iterator.GetNextByte()); + iterator.Skip(1); + EXPECT_TRUE(iterator.Done()); +} + +TEST_F(EhFrameIteratorTest, ULEB128Decoding) { + static const byte kEncoded[] = {0xe5, 0x8e, 0x26}; + EhFrameIterator iterator(&kEncoded[0], &kEncoded[0] + sizeof(kEncoded)); + EXPECT_EQ(624485, iterator.GetNextULeb128()); + EXPECT_TRUE(iterator.Done()); +} + +TEST_F(EhFrameIteratorTest, SLEB128DecodingPositive) { + static const byte kEncoded[] = {0xe5, 0x8e, 0x26}; + EhFrameIterator iterator(&kEncoded[0], &kEncoded[0] + sizeof(kEncoded)); + EXPECT_EQ(624485, iterator.GetNextSLeb128()); + EXPECT_TRUE(iterator.Done()); +} + +TEST_F(EhFrameIteratorTest, SLEB128DecodingNegative) { + static const byte kEncoded[] = {0x9b, 0xf1, 0x59}; + EhFrameIterator iterator(&kEncoded[0], &kEncoded[0] + sizeof(kEncoded)); + EXPECT_EQ(-624485, iterator.GetNextSLeb128()); + EXPECT_TRUE(iterator.Done()); +} + +#endif diff --git a/test/unittests/eh-frame-writer-unittest.cc b/test/unittests/eh-frame-writer-unittest.cc new file mode 100644 index 0000000000..24eeca45aa --- /dev/null +++ b/test/unittests/eh-frame-writer-unittest.cc @@ -0,0 +1,464 @@ +// 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 "test/unittests/test-utils.h" + +// Test enabled only on supported architectures. +#if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM) || \ + defined(V8_TARGET_ARCH_ARM64) + +using namespace v8::internal; + +namespace { + +class EhFrameWriterTest : public TestWithZone { + protected: + // Being a 7bit positive integer, this also serves as its ULEB128 encoding. + static const int kTestRegisterCode = 0; + + static EhFrameIterator MakeIterator(EhFrameWriter* writer) { + CodeDesc desc; + writer->GetEhFrame(&desc); + DCHECK_GT(desc.unwinding_info_size, 0); + return EhFrameIterator(desc.unwinding_info, + desc.unwinding_info + desc.unwinding_info_size); + } +}; + +const int EhFrameWriterTest::kTestRegisterCode; + +} // namespace + +TEST_F(EhFrameWriterTest, Alignment) { + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(42 * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + ASSERT_EQ(0, EhFrameConstants::kEhFrameHdrSize % 4); + ASSERT_EQ(0, EhFrameConstants::kEhFrameTerminatorSize % 4); + EXPECT_EQ(0, (iterator.GetBufferSize() - EhFrameConstants::kEhFrameHdrSize - + EhFrameConstants::kEhFrameTerminatorSize) % + 8); +} + +TEST_F(EhFrameWriterTest, FDEHeader) { + static const int kProcedureSize = 0x5678abcd; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.Finish(kProcedureSize); + + EhFrameIterator iterator = MakeIterator(&writer); + int cie_size = iterator.GetNextUInt32(); + iterator.Skip(cie_size); + + int fde_size = iterator.GetNextUInt32(); + EXPECT_EQ(iterator.GetBufferSize(), + fde_size + cie_size + EhFrameConstants::kEhFrameTerminatorSize + + EhFrameConstants::kEhFrameHdrSize + 2 * kInt32Size); + + int backwards_offset_to_cie_offset = iterator.GetCurrentOffset(); + int backwards_offset_to_cie = iterator.GetNextUInt32(); + EXPECT_EQ(backwards_offset_to_cie_offset, backwards_offset_to_cie); + + int procedure_address_offset = iterator.GetCurrentOffset(); + int procedure_address = iterator.GetNextUInt32(); + EXPECT_EQ(-(procedure_address_offset + RoundUp(kProcedureSize, 8)), + procedure_address); + + int procedure_size = iterator.GetNextUInt32(); + EXPECT_EQ(kProcedureSize, procedure_size); +} + +TEST_F(EhFrameWriterTest, SetOffset) { + static const int kOffset = 0x0badc0de; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.SetBaseAddressOffset(kOffset); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kDefCfaOffset, + iterator.GetNextOpcode()); + EXPECT_EQ(kOffset, iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, IncreaseOffset) { + static const int kFirstOffset = 121; + static const int kSecondOffset = 16; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.SetBaseAddressOffset(kFirstOffset); + writer.IncreaseBaseAddressOffset(kSecondOffset); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kDefCfaOffset, + iterator.GetNextOpcode()); + EXPECT_EQ(kFirstOffset, iterator.GetNextULeb128()); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kDefCfaOffset, + iterator.GetNextOpcode()); + EXPECT_EQ(kFirstOffset + kSecondOffset, iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, SetRegister) { + Register test_register = Register::from_code(kTestRegisterCode); + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.SetBaseAddressRegister(test_register); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kDefCfaRegister, + iterator.GetNextOpcode()); + EXPECT_EQ(kTestRegisterCode, iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, SetRegisterAndOffset) { + Register test_register = Register::from_code(kTestRegisterCode); + static const int kOffset = 0x0badc0de; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.SetBaseAddressRegisterAndOffset(test_register, kOffset); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kDefCfa, iterator.GetNextOpcode()); + EXPECT_EQ(kTestRegisterCode, iterator.GetNextULeb128()); + EXPECT_EQ(kOffset, iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding6bit) { + static const int kOffset = 42; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ((1 << 6) | kOffset, iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding6bitDelta) { + static const int kFirstOffset = 42; + static const int kSecondOffset = 62; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kFirstOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.AdvanceLocation(kSecondOffset * + EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ((1 << 6) | kFirstOffset, iterator.GetNextByte()); + EXPECT_EQ((1 << 6) | (kSecondOffset - kFirstOffset), iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding8bit) { + static const int kOffset = 0x42; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc1, + iterator.GetNextOpcode()); + EXPECT_EQ(kOffset, iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding8bitDelta) { + static const int kFirstOffset = 0x10; + static const int kSecondOffset = 0x70; + static const int kThirdOffset = 0xb5; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kFirstOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.AdvanceLocation(kSecondOffset * + EhFrameConstants::kCodeAlignmentFactor); + writer.AdvanceLocation(kThirdOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ((1 << 6) | kFirstOffset, iterator.GetNextByte()); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc1, + iterator.GetNextOpcode()); + EXPECT_EQ(kSecondOffset - kFirstOffset, iterator.GetNextByte()); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc1, + iterator.GetNextOpcode()); + EXPECT_EQ(kThirdOffset - kSecondOffset, iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding16bit) { + static const int kOffset = kMaxUInt8 + 42; + ASSERT_LT(kOffset, kMaxUInt16); + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc2, + iterator.GetNextOpcode()); + EXPECT_EQ(kOffset, iterator.GetNextUInt16()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding16bitDelta) { + static const int kFirstOffset = 0x41; + static const int kSecondOffset = kMaxUInt8 + 0x42; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kFirstOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.AdvanceLocation(kSecondOffset * + EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc1, + iterator.GetNextOpcode()); + EXPECT_EQ(kFirstOffset, iterator.GetNextByte()); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc2, + iterator.GetNextOpcode()); + EXPECT_EQ(kSecondOffset - kFirstOffset, iterator.GetNextUInt16()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding32bit) { + static const int kOffset = kMaxUInt16 + 42; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc4, + iterator.GetNextOpcode()); + EXPECT_EQ(kOffset, iterator.GetNextUInt32()); +} + +TEST_F(EhFrameWriterTest, PcOffsetEncoding32bitDelta) { + static const int kFirstOffset = kMaxUInt16 + 0x42; + static const int kSecondOffset = kMaxUInt16 + 0x67; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.AdvanceLocation(kFirstOffset * EhFrameConstants::kCodeAlignmentFactor); + writer.AdvanceLocation(kSecondOffset * + EhFrameConstants::kCodeAlignmentFactor); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kAdvanceLoc4, + iterator.GetNextOpcode()); + EXPECT_EQ(kFirstOffset, iterator.GetNextUInt32()); + + EXPECT_EQ((1 << 6) | (kSecondOffset - kFirstOffset), iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, SaveRegisterUnsignedOffset) { + Register test_register = Register::from_code(kTestRegisterCode); + static const int kOffset = + EhFrameConstants::kDataAlignmentFactor > 0 ? 12344 : -12344; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.RecordRegisterSavedToStack(test_register, kOffset); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ((2 << 6) | kTestRegisterCode, iterator.GetNextByte()); + EXPECT_EQ(kOffset / EhFrameConstants::kDataAlignmentFactor, + iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, SaveRegisterSignedOffset) { + Register test_register = Register::from_code(kTestRegisterCode); + static const int kOffset = + EhFrameConstants::kDataAlignmentFactor < 0 ? 12344 : -12344; + + ASSERT_EQ(kOffset % EhFrameConstants::kDataAlignmentFactor, 0); + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.RecordRegisterSavedToStack(test_register, kOffset); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kOffsetExtendedSf, + iterator.GetNextOpcode()); + EXPECT_EQ(kTestRegisterCode, iterator.GetNextULeb128()); + EXPECT_EQ(kOffset / EhFrameConstants::kDataAlignmentFactor, + iterator.GetNextSLeb128()); +} + +TEST_F(EhFrameWriterTest, RegisterNotModified) { + Register test_register = Register::from_code(kTestRegisterCode); + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.RecordRegisterNotModified(test_register); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ(EhFrameConstants::DwarfOpcodes::kSameValue, + iterator.GetNextOpcode()); + EXPECT_EQ(kTestRegisterCode, iterator.GetNextULeb128()); +} + +TEST_F(EhFrameWriterTest, RegisterFollowsInitialRule) { + Register test_register = Register::from_code(kTestRegisterCode); + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.RecordRegisterFollowsInitialRule(test_register); + writer.Finish(100); + + EhFrameIterator iterator = MakeIterator(&writer); + iterator.SkipToFdeDirectives(); + + EXPECT_EQ((3 << 6) | kTestRegisterCode, iterator.GetNextByte()); +} + +TEST_F(EhFrameWriterTest, EhFrameHdrLayout) { + static const int kCodeSize = 10; + static const int kPaddingSize = 6; + + EhFrameWriter writer(zone()); + writer.Initialize(); + writer.Finish(kCodeSize); + + EhFrameIterator iterator = MakeIterator(&writer); + + // Skip the .eh_frame. + + int encoded_cie_size = iterator.GetNextUInt32(); + iterator.Skip(encoded_cie_size); + int cie_size = encoded_cie_size + kInt32Size; + + int encoded_fde_size = iterator.GetNextUInt32(); + iterator.Skip(encoded_fde_size); + int fde_size = encoded_fde_size + kInt32Size; + + iterator.Skip(EhFrameConstants::kEhFrameTerminatorSize); + + int eh_frame_size = + cie_size + fde_size + EhFrameConstants::kEhFrameTerminatorSize; + + // + // Plugging some numbers in the DSO layout shown in eh-frame.cc: + // + // | ... | + // +---------------+ <-- (E) --------- + // | | ^ + // | Instructions | 10 bytes | .text + // | | v + // +---------------+ <---------------- + // |///////////////| + // |////Padding////| 6 bytes + // |///////////////| + // +---------------+ <---(D)---------- + // | | ^ + // | CIE | cie_size bytes* | + // | | | + // +---------------+ <-- (C) | + // | | | .eh_frame + // | FDE | fde_size bytes | + // | | | + // +---------------+ | + // | terminator | 4 bytes v + // +---------------+ <-- (B) --------- + // | version | ^ + // +---------------+ 4 bytes | + // | encoding | | + // | specifiers | | + // +---------------+ <---(A) | .eh_frame_hdr + // | offset to | | + // | .eh_frame | | + // +---------------+ | + // | ... | ... + // + // (*) the size of the CIE is platform dependent. + // + + int eh_frame_hdr_version = iterator.GetNextByte(); + EXPECT_EQ(EhFrameConstants::kEhFrameHdrVersion, eh_frame_hdr_version); + + // .eh_frame pointer encoding specifier. + EXPECT_EQ(EhFrameConstants::kSData4 | EhFrameConstants::kPcRel, + iterator.GetNextByte()); + + // Lookup table size encoding specifier. + EXPECT_EQ(EhFrameConstants::kUData4, iterator.GetNextByte()); + + // Lookup table pointers encoding specifier. + EXPECT_EQ(EhFrameConstants::kSData4 | EhFrameConstants::kDataRel, + iterator.GetNextByte()); + + // A -> D + int offset_to_eh_frame = iterator.GetNextUInt32(); + EXPECT_EQ(-(EhFrameConstants::kFdeVersionSize + + EhFrameConstants::kFdeEncodingSpecifiersSize + eh_frame_size), + offset_to_eh_frame); + + int lut_entries = iterator.GetNextUInt32(); + EXPECT_EQ(1, lut_entries); + + // B -> E + int offset_to_procedure = iterator.GetNextUInt32(); + EXPECT_EQ(-(eh_frame_size + kPaddingSize + kCodeSize), offset_to_procedure); + + // B -> C + int offset_to_fde = iterator.GetNextUInt32(); + EXPECT_EQ(-(fde_size + EhFrameConstants::kEhFrameTerminatorSize), + offset_to_fde); +} + +#endif diff --git a/test/unittests/unittests.gyp b/test/unittests/unittests.gyp index a4d3b9ca00..09c5b129cd 100644 --- a/test/unittests/unittests.gyp +++ b/test/unittests/unittests.gyp @@ -79,6 +79,8 @@ 'compiler/value-numbering-reducer-unittest.cc', 'compiler/zone-pool-unittest.cc', 'counters-unittest.cc', + 'eh-frame-iterator-unittest.cc', + 'eh-frame-writer-unittest.cc', 'interpreter/bytecodes-unittest.cc', 'interpreter/bytecode-array-builder-unittest.cc', 'interpreter/bytecode-array-iterator-unittest.cc',