//===-- ArmUnwindInfo.cpp -------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #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 value 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. llvm::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> 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) == 0xd0) { // 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(); 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.SetUnwindPlanForSignalTrap(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()); }