/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ART_LIBELFFILE_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_ #define ART_LIBELFFILE_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_ #include "base/bit_utils.h" #include "dwarf/dwarf_constants.h" #include "dwarf/register.h" #include "dwarf/writer.h" namespace art { namespace dwarf { // Writer for .debug_frame opcodes (DWARF-3). // See the DWARF specification for the precise meaning of the opcodes. // The writer is very light-weight, however it will do the following for you: // * Choose the most compact encoding of a given opcode. // * Keep track of current state and convert absolute values to deltas. // * Divide by header-defined factors as appropriate. template > class DebugFrameOpCodeWriter : private Writer { static_assert(std::is_same::value, "Invalid value type"); public: // To save space, DWARF divides most offsets by header-defined factors. // They are used in integer divisions, so we make them constants. // We usually subtract from stack base pointer, so making the factor // negative makes the encoded values positive and thus easier to encode. static constexpr int kDataAlignmentFactor = -4; static constexpr int kCodeAlignmentFactor = 1; // Explicitely advance the program counter to given location. void ALWAYS_INLINE AdvancePC(int absolute_pc) { DCHECK_GE(absolute_pc, current_pc_); if (UNLIKELY(enabled_)) { int delta = FactorCodeOffset(absolute_pc - current_pc_); if (delta != 0) { if (delta <= 0x3F) { this->PushUint8(DW_CFA_advance_loc | delta); } else if (delta <= UINT8_MAX) { this->PushUint8(DW_CFA_advance_loc1); this->PushUint8(delta); } else if (delta <= UINT16_MAX) { this->PushUint8(DW_CFA_advance_loc2); this->PushUint16(delta); } else { this->PushUint8(DW_CFA_advance_loc4); this->PushUint32(delta); } } current_pc_ = absolute_pc; } } // Override this method to automatically advance the PC before each opcode. virtual void ImplicitlyAdvancePC() { } // Common alias in assemblers - spill relative to current stack pointer. void ALWAYS_INLINE RelOffset(Reg reg, int offset) { Offset(reg, offset - current_cfa_offset_); } // Common alias in assemblers - increase stack frame size. void ALWAYS_INLINE AdjustCFAOffset(int delta) { DefCFAOffset(current_cfa_offset_ + delta); } // Custom alias - spill many registers based on bitmask. void ALWAYS_INLINE RelOffsetForMany(Reg reg_base, int32_t offset, uint32_t reg_mask, int32_t reg_size) { DCHECK(reg_size == 4 || reg_size == 8); if (UNLIKELY(enabled_)) { for (int i = 0; reg_mask != 0u; reg_mask >>= 1, i++) { // Skip zero bits and go to the set bit. int num_zeros = CTZ(reg_mask); i += num_zeros; reg_mask >>= num_zeros; RelOffset(Reg(reg_base.num() + i), offset); offset += reg_size; } } } // Custom alias - unspill many registers based on bitmask. void ALWAYS_INLINE RestoreMany(Reg reg_base, uint32_t reg_mask) { if (UNLIKELY(enabled_)) { for (int i = 0; reg_mask != 0u; reg_mask >>= 1, i++) { // Skip zero bits and go to the set bit. int num_zeros = CTZ(reg_mask); i += num_zeros; reg_mask >>= num_zeros; Restore(Reg(reg_base.num() + i)); } } } void ALWAYS_INLINE Nop() { if (UNLIKELY(enabled_)) { this->PushUint8(DW_CFA_nop); } } void ALWAYS_INLINE Offset(Reg reg, int offset) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); int factored_offset = FactorDataOffset(offset); // May change sign. if (factored_offset >= 0) { if (0 <= reg.num() && reg.num() <= 0x3F) { this->PushUint8(DW_CFA_offset | reg.num()); this->PushUleb128(factored_offset); } else { this->PushUint8(DW_CFA_offset_extended); this->PushUleb128(reg.num()); this->PushUleb128(factored_offset); } } else { uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_offset_extended_sf); this->PushUleb128(reg.num()); this->PushSleb128(factored_offset); } } } void ALWAYS_INLINE Restore(Reg reg) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); if (0 <= reg.num() && reg.num() <= 0x3F) { this->PushUint8(DW_CFA_restore | reg.num()); } else { this->PushUint8(DW_CFA_restore_extended); this->PushUleb128(reg.num()); } } } void ALWAYS_INLINE Undefined(Reg reg) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_undefined); this->PushUleb128(reg.num()); } } void ALWAYS_INLINE SameValue(Reg reg) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_same_value); this->PushUleb128(reg.num()); } } // The previous value of "reg" is stored in register "new_reg". void ALWAYS_INLINE Register(Reg reg, Reg new_reg) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_register); this->PushUleb128(reg.num()); this->PushUleb128(new_reg.num()); } } void ALWAYS_INLINE RememberState() { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_remember_state); } } void ALWAYS_INLINE RestoreState() { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_restore_state); } } void ALWAYS_INLINE DefCFA(Reg reg, int offset) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); if (offset >= 0) { this->PushUint8(DW_CFA_def_cfa); this->PushUleb128(reg.num()); this->PushUleb128(offset); // Non-factored. } else { uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_def_cfa_sf); this->PushUleb128(reg.num()); this->PushSleb128(FactorDataOffset(offset)); } } current_cfa_offset_ = offset; } void ALWAYS_INLINE DefCFARegister(Reg reg) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); this->PushUint8(DW_CFA_def_cfa_register); this->PushUleb128(reg.num()); } } void ALWAYS_INLINE DefCFAOffset(int offset) { if (UNLIKELY(enabled_)) { if (current_cfa_offset_ != offset) { ImplicitlyAdvancePC(); if (offset >= 0) { this->PushUint8(DW_CFA_def_cfa_offset); this->PushUleb128(offset); // Non-factored. } else { uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_def_cfa_offset_sf); this->PushSleb128(FactorDataOffset(offset)); } } } // Uncoditional so that the user can still get and check the value. current_cfa_offset_ = offset; } void ALWAYS_INLINE ValOffset(Reg reg, int offset) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); uses_dwarf3_features_ = true; int factored_offset = FactorDataOffset(offset); // May change sign. if (factored_offset >= 0) { this->PushUint8(DW_CFA_val_offset); this->PushUleb128(reg.num()); this->PushUleb128(factored_offset); } else { this->PushUint8(DW_CFA_val_offset_sf); this->PushUleb128(reg.num()); this->PushSleb128(factored_offset); } } } void ALWAYS_INLINE DefCFAExpression(uint8_t* expr, int expr_size) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_def_cfa_expression); this->PushUleb128(expr_size); this->PushData(expr, expr_size); } } void ALWAYS_INLINE Expression(Reg reg, uint8_t* expr, int expr_size) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_expression); this->PushUleb128(reg.num()); this->PushUleb128(expr_size); this->PushData(expr, expr_size); } } void ALWAYS_INLINE ValExpression(Reg reg, uint8_t* expr, int expr_size) { if (UNLIKELY(enabled_)) { ImplicitlyAdvancePC(); uses_dwarf3_features_ = true; this->PushUint8(DW_CFA_val_expression); this->PushUleb128(reg.num()); this->PushUleb128(expr_size); this->PushData(expr, expr_size); } } bool IsEnabled() const { return enabled_; } void SetEnabled(bool value) { enabled_ = value; if (enabled_ && opcodes_.capacity() == 0u) { opcodes_.reserve(kDefaultCapacity); } } int GetCurrentPC() const { return current_pc_; } int GetCurrentCFAOffset() const { return current_cfa_offset_; } void SetCurrentCFAOffset(int offset) { current_cfa_offset_ = offset; } using Writer::data; explicit DebugFrameOpCodeWriter(bool enabled = true, const typename Vector::allocator_type& alloc = typename Vector::allocator_type()) : Writer(&opcodes_), enabled_(false), opcodes_(alloc), current_cfa_offset_(0), current_pc_(0), uses_dwarf3_features_(false) { SetEnabled(enabled); } virtual ~DebugFrameOpCodeWriter() { } protected: // Best guess based on couple of observed outputs. static constexpr size_t kDefaultCapacity = 32u; int FactorDataOffset(int offset) const { DCHECK_EQ(offset % kDataAlignmentFactor, 0); return offset / kDataAlignmentFactor; } int FactorCodeOffset(int offset) const { DCHECK_EQ(offset % kCodeAlignmentFactor, 0); return offset / kCodeAlignmentFactor; } bool enabled_; // If disabled all writes are no-ops. Vector opcodes_; int current_cfa_offset_; int current_pc_; bool uses_dwarf3_features_; private: DISALLOW_COPY_AND_ASSIGN(DebugFrameOpCodeWriter); }; } // namespace dwarf } // namespace art #endif // ART_LIBELFFILE_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_