| //===-- ArmUnwindInfo.cpp ---------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <vector> |
| |
| #include "Utility/ARM_DWARF_Registers.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/Section.h" |
| #include "lldb/Symbol/ArmUnwindInfo.h" |
| #include "lldb/Symbol/SymbolVendor.h" |
| #include "lldb/Symbol/UnwindPlan.h" |
| #include "lldb/Utility/Endian.h" |
| |
| /* |
| * Unwind information reader and parser for the ARM exception handling ABI |
| * |
| * Implemented based on: |
| * Exception Handling ABI for the ARM Architecture |
| * Document number: ARM IHI 0038A (current through ABI r2.09) |
| * Date of Issue: 25th January 2007, reissued 30th November 2012 |
| * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf |
| */ |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| // Converts a prel31 avlue to lldb::addr_t with sign extension |
| static addr_t Prel31ToAddr(uint32_t prel31) { |
| addr_t res = prel31; |
| if (prel31 & (1 << 30)) |
| res |= 0xffffffff80000000ULL; |
| return res; |
| } |
| |
| ArmUnwindInfo::ArmExidxEntry::ArmExidxEntry(uint32_t f, lldb::addr_t a, |
| uint32_t d) |
| : file_address(f), address(a), data(d) {} |
| |
| bool ArmUnwindInfo::ArmExidxEntry::operator<(const ArmExidxEntry &other) const { |
| return address < other.address; |
| } |
| |
| ArmUnwindInfo::ArmUnwindInfo(ObjectFile &objfile, SectionSP &arm_exidx, |
| SectionSP &arm_extab) |
| : m_byte_order(objfile.GetByteOrder()), m_arm_exidx_sp(arm_exidx), |
| m_arm_extab_sp(arm_extab) { |
| objfile.ReadSectionData(arm_exidx.get(), m_arm_exidx_data); |
| objfile.ReadSectionData(arm_extab.get(), m_arm_extab_data); |
| |
| addr_t exidx_base_addr = m_arm_exidx_sp->GetFileAddress(); |
| |
| offset_t offset = 0; |
| while (m_arm_exidx_data.ValidOffset(offset)) { |
| lldb::addr_t file_addr = exidx_base_addr + offset; |
| lldb::addr_t addr = exidx_base_addr + (addr_t)offset + |
| Prel31ToAddr(m_arm_exidx_data.GetU32(&offset)); |
| uint32_t data = m_arm_exidx_data.GetU32(&offset); |
| m_exidx_entries.emplace_back(file_addr, addr, data); |
| } |
| |
| // Sort the entries in the exidx section. The entries should be sorted inside |
| // the section but some old compiler isn't sorted them. |
| std::sort(m_exidx_entries.begin(), m_exidx_entries.end()); |
| } |
| |
| ArmUnwindInfo::~ArmUnwindInfo() {} |
| |
| // Read a byte from the unwind instruction stream with the given offset. Custom |
| // function is required because have to red in order of significance within |
| // their containing word (most significant byte first) and in increasing word |
| // address order. |
| uint8_t ArmUnwindInfo::GetByteAtOffset(const uint32_t *data, |
| uint16_t offset) const { |
| uint32_t value = data[offset / 4]; |
| if (m_byte_order != endian::InlHostByteOrder()) |
| value = llvm::ByteSwap_32(value); |
| return (value >> ((3 - (offset % 4)) * 8)) & 0xff; |
| } |
| |
| uint64_t ArmUnwindInfo::GetULEB128(const uint32_t *data, uint16_t &offset, |
| uint16_t max_offset) const { |
| uint64_t result = 0; |
| uint8_t shift = 0; |
| while (offset < max_offset) { |
| uint8_t byte = GetByteAtOffset(data, offset++); |
| result |= (uint64_t)(byte & 0x7f) << shift; |
| if ((byte & 0x80) == 0) |
| break; |
| shift += 7; |
| } |
| return result; |
| } |
| |
| bool ArmUnwindInfo::GetUnwindPlan(Target &target, const Address &addr, |
| UnwindPlan &unwind_plan) { |
| const uint32_t *data = (const uint32_t *)GetExceptionHandlingTableEntry(addr); |
| if (data == nullptr) |
| return false; // No unwind information for the function |
| |
| if (data[0] == 0x1) |
| return false; // EXIDX_CANTUNWIND |
| |
| uint16_t byte_count = 0; |
| uint16_t byte_offset = 0; |
| if (data[0] & 0x80000000) { |
| switch ((data[0] >> 24) & 0x0f) { |
| case 0: |
| byte_count = 4; |
| byte_offset = 1; |
| break; |
| case 1: |
| case 2: |
| byte_count = 4 * ((data[0] >> 16) & 0xff) + 4; |
| byte_offset = 2; |
| break; |
| default: |
| // Unhandled personality routine index |
| return false; |
| } |
| } else { |
| byte_count = 4 * ((data[1] >> 24) & 0xff) + 8; |
| byte_offset = 5; |
| } |
| |
| uint8_t vsp_reg = dwarf_sp; |
| int32_t vsp = 0; |
| std::vector<std::pair<uint32_t, int32_t>> |
| register_offsets; // register -> (offset from vsp_reg) |
| |
| while (byte_offset < byte_count) { |
| uint8_t byte1 = GetByteAtOffset(data, byte_offset++); |
| if ((byte1 & 0xc0) == 0x00) { |
| // 00xxxxxx |
| // vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive |
| vsp += ((byte1 & 0x3f) << 2) + 4; |
| } else if ((byte1 & 0xc0) == 0x40) { |
| // 01xxxxxx |
| // vsp = vsp – (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive |
| vsp -= ((byte1 & 0x3f) << 2) + 4; |
| } else if ((byte1 & 0xf0) == 0x80) { |
| if (byte_offset >= byte_count) |
| return false; |
| |
| uint8_t byte2 = GetByteAtOffset(data, byte_offset++); |
| if (byte1 == 0x80 && byte2 == 0) { |
| // 10000000 00000000 |
| // Refuse to unwind (for example, out of a cleanup) (see remark a) |
| return false; |
| } else { |
| // 1000iiii iiiiiiii (i not all 0) |
| // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4} (see |
| // remark b) |
| uint16_t regs = ((byte1 & 0x0f) << 8) | byte2; |
| for (uint8_t i = 0; i < 12; ++i) { |
| if (regs & (1 << i)) { |
| register_offsets.emplace_back(dwarf_r4 + i, vsp); |
| vsp += 4; |
| } |
| } |
| } |
| } else if ((byte1 & 0xff) == 0x9d) { |
| // 10011101 |
| // Reserved as prefix for ARM register to register moves |
| return false; |
| } else if ((byte1 & 0xff) == 0x9f) { |
| // 10011111 |
| // Reserved as prefix for Intel Wireless MMX register to register moves |
| return false; |
| } else if ((byte1 & 0xf0) == 0x90) { |
| // 1001nnnn (nnnn != 13,15) |
| // Set vsp = r[nnnn] |
| vsp_reg = dwarf_r0 + (byte1 & 0x0f); |
| } else if ((byte1 & 0xf8) == 0xa0) { |
| // 10100nnn |
| // Pop r4-r[4+nnn] |
| uint8_t n = byte1 & 0x7; |
| for (uint8_t i = 0; i <= n; ++i) { |
| register_offsets.emplace_back(dwarf_r4 + i, vsp); |
| vsp += 4; |
| } |
| } else if ((byte1 & 0xf8) == 0xa8) { |
| // 10101nnn |
| // Pop r4-r[4+nnn], r14 |
| uint8_t n = byte1 & 0x7; |
| for (uint8_t i = 0; i <= n; ++i) { |
| register_offsets.emplace_back(dwarf_r4 + i, vsp); |
| vsp += 4; |
| } |
| |
| register_offsets.emplace_back(dwarf_lr, vsp); |
| vsp += 4; |
| } else if ((byte1 & 0xff) == 0xb0) { |
| // 10110000 |
| // Finish (see remark c) |
| break; |
| } else if ((byte1 & 0xff) == 0xb1) { |
| if (byte_offset >= byte_count) |
| return false; |
| |
| uint8_t byte2 = GetByteAtOffset(data, byte_offset++); |
| if ((byte2 & 0xff) == 0x00) { |
| // 10110001 00000000 |
| // Spare (see remark f) |
| return false; |
| } else if ((byte2 & 0xf0) == 0x00) { |
| // 10110001 0000iiii (i not all 0) |
| // Pop integer registers under mask {r3, r2, r1, r0} |
| for (uint8_t i = 0; i < 4; ++i) { |
| if (byte2 & (1 << i)) { |
| register_offsets.emplace_back(dwarf_r0 + i, vsp); |
| vsp += 4; |
| } |
| } |
| } else { |
| // 10110001 xxxxyyyy |
| // Spare (xxxx != 0000) |
| return false; |
| } |
| } else if ((byte1 & 0xff) == 0xb2) { |
| // 10110010 uleb128 |
| // vsp = vsp + 0x204+ (uleb128 << 2) |
| uint64_t uleb128 = GetULEB128(data, byte_offset, byte_count); |
| vsp += 0x204 + (uleb128 << 2); |
| } else if ((byte1 & 0xff) == 0xb3) { |
| // 10110011 sssscccc |
| // Pop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if) |
| // by FSTMFDX (see remark d) |
| if (byte_offset >= byte_count) |
| return false; |
| |
| uint8_t byte2 = GetByteAtOffset(data, byte_offset++); |
| uint8_t s = (byte2 & 0xf0) >> 4; |
| uint8_t c = (byte2 & 0x0f) >> 0; |
| for (uint8_t i = 0; i <= c; ++i) { |
| register_offsets.emplace_back(dwarf_d0 + s + i, vsp); |
| vsp += 8; |
| } |
| vsp += 4; |
| } else if ((byte1 & 0xfc) == 0xb4) { |
| // 101101nn |
| // Spare (was Pop FPA) |
| return false; |
| } else if ((byte1 & 0xf8) == 0xb8) { |
| // 10111nnn |
| // Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by |
| // FSTMFDX (see remark d) |
| uint8_t n = byte1 & 0x07; |
| for (uint8_t i = 0; i <= n; ++i) { |
| register_offsets.emplace_back(dwarf_d8 + i, vsp); |
| vsp += 8; |
| } |
| vsp += 4; |
| } else if ((byte1 & 0xf8) == 0xc0) { |
| // 11000nnn (nnn != 6,7) |
| // Intel Wireless MMX pop wR[10]-wR[10+nnn] |
| |
| // 11000110 sssscccc |
| // Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc] (see remark e) |
| |
| // 11000111 00000000 |
| // Spare |
| |
| // 11000111 0000iiii |
| // Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0} |
| |
| // 11000111 xxxxyyyy |
| // Spare (xxxx != 0000) |
| |
| return false; |
| } else if ((byte1 & 0xff) == 0xc8) { |
| // 11001000 sssscccc |
| // Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] saved |
| // (as if) by FSTMFDD (see remarks d,e) |
| if (byte_offset >= byte_count) |
| return false; |
| |
| uint8_t byte2 = GetByteAtOffset(data, byte_offset++); |
| uint8_t s = (byte2 & 0xf0) >> 4; |
| uint8_t c = (byte2 & 0x0f) >> 0; |
| for (uint8_t i = 0; i <= c; ++i) { |
| register_offsets.emplace_back(dwarf_d16 + s + i, vsp); |
| vsp += 8; |
| } |
| } else if ((byte1 & 0xff) == 0xc9) { |
| // 11001001 sssscccc |
| // Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) |
| // by FSTMFDD (see remark d) |
| if (byte_offset >= byte_count) |
| return false; |
| |
| uint8_t byte2 = GetByteAtOffset(data, byte_offset++); |
| uint8_t s = (byte2 & 0xf0) >> 4; |
| uint8_t c = (byte2 & 0x0f) >> 0; |
| for (uint8_t i = 0; i <= c; ++i) { |
| register_offsets.emplace_back(dwarf_d0 + s + i, vsp); |
| vsp += 8; |
| } |
| } else if ((byte1 & 0xf8) == 0xc8) { |
| // 11001yyy |
| // Spare (yyy != 000, 001) |
| return false; |
| } else if ((byte1 & 0xf8) == 0xc0) { |
| // 11010nnn |
| // Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by |
| // FSTMFDD (see remark d) |
| uint8_t n = byte1 & 0x07; |
| for (uint8_t i = 0; i <= n; ++i) { |
| register_offsets.emplace_back(dwarf_d8 + i, vsp); |
| vsp += 8; |
| } |
| } else if ((byte1 & 0xc0) == 0xc0) { |
| // 11xxxyyy Spare (xxx != 000, 001, 010) |
| return false; |
| } else { |
| return false; |
| } |
| } |
| |
| UnwindPlan::RowSP row = std::make_shared<UnwindPlan::Row>(); |
| row->SetOffset(0); |
| row->GetCFAValue().SetIsRegisterPlusOffset(vsp_reg, vsp); |
| |
| bool have_location_for_pc = false; |
| for (const auto &offset : register_offsets) { |
| have_location_for_pc |= offset.first == dwarf_pc; |
| row->SetRegisterLocationToAtCFAPlusOffset(offset.first, offset.second - vsp, |
| true); |
| } |
| |
| if (!have_location_for_pc) { |
| UnwindPlan::Row::RegisterLocation lr_location; |
| if (row->GetRegisterInfo(dwarf_lr, lr_location)) |
| row->SetRegisterInfo(dwarf_pc, lr_location); |
| else |
| row->SetRegisterLocationToRegister(dwarf_pc, dwarf_lr, false); |
| } |
| |
| unwind_plan.AppendRow(row); |
| unwind_plan.SetSourceName("ARM.exidx unwind info"); |
| unwind_plan.SetSourcedFromCompiler(eLazyBoolYes); |
| unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo); |
| unwind_plan.SetRegisterKind(eRegisterKindDWARF); |
| |
| return true; |
| } |
| |
| const uint8_t * |
| ArmUnwindInfo::GetExceptionHandlingTableEntry(const Address &addr) { |
| auto it = std::upper_bound(m_exidx_entries.begin(), m_exidx_entries.end(), |
| ArmExidxEntry{0, addr.GetFileAddress(), 0}); |
| if (it == m_exidx_entries.begin()) |
| return nullptr; |
| --it; |
| |
| if (it->data == 0x1) |
| return nullptr; // EXIDX_CANTUNWIND |
| |
| if (it->data & 0x80000000) |
| return (const uint8_t *)&it->data; |
| |
| addr_t data_file_addr = it->file_address + 4 + Prel31ToAddr(it->data); |
| return m_arm_extab_data.GetDataStart() + |
| (data_file_addr - m_arm_extab_sp->GetFileAddress()); |
| } |