From f9d8cb865a7ec73945743b27fb8090b37a91f9b1 Mon Sep 17 00:00:00 2001 From: Jason Molenda Date: Tue, 9 Sep 2025 14:11:39 -0700 Subject: [PATCH] [lldb] Unwind through ARM Cortex-M exceptions automatically (#153922) When a processor faults/is interrupted/gets an exception, it will stop running code and jump to an exception catcher routine. Most processors will store the pc that was executing in a system register, and the catcher functions have special instructions to retrieve that & possibly other registers. It may then save those values to stack, and the author can add .cfi directives to tell lldb's unwinder where to find those saved values. ARM Cortex-M (microcontroller) processors have a simpler mechanism where a fixed set of registers are saved to the stack on an exception, and a unique value is put in the link register to indicate to the caller that this has taken place. No special handling needs to be written into the exception catcher, unless it wants to inspect these preserved values. And it is possible for a general stack walker to walk the stack with no special knowledge about what the catch function does. This patch adds an Architecture plugin method to allow an Architecture to override/augment the UnwindPlan that lldb would use for a stack frame, given the contents of the return address register. It resembles a feature where the LanguageRuntime can replace/augment the unwind plan for a function, but it is doing it at offset by one level. The LanguageRuntime is looking at the local register context and/or symbol name to decide if it will override the unwind rules. For the Cortex-M exception unwinds, we need to modify THIS frame's unwind plan if the CALLER's LR had a specific value. RegisterContextUnwind has to retrieve the caller's LR value before it has completely decided on the UnwindPlan it will use for THIS stack frame. This does mean that we will need one additional read of stack memory than we currently do when unwinding, on Armv7 Cortex-M targets. The unwinder walks the stack lazily, as stack frames are requested, and so now if you ask for 2 stack frames, we will read enough stack to walk 2 frames, plus we will read one extra word of memory, the spilled LR value from the stack. In practice, with 512-byte memory cache reads, this is unlikely to be a real performance hit. This PR includes a test with a yaml corefile description and a JSON ObjectFile, incorporating all of the necessary stack memory and symbol names from a real debug session I worked on. The architectural default unwind plans are used for all stack frames except the 0th because there's no instructions for the functions, and no unwind info. I may need to add an encoding of unwind fules to ObjectFileJSON in the future as we create more test cases like this. This PR depends on the yaml2macho-core utility from https://github.com/llvm/llvm-project/pull/153911 to run its API test. rdar://110663219 (cherry picked from commit 69511ae80480815385abef7dfa095a8bd2961144) --- lldb/include/lldb/Core/Architecture.h | 9 + .../lldb/Target/RegisterContextUnwind.h | 45 +++-- lldb/include/lldb/Target/UnwindLLDB.h | 2 + lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp | 7 + .../Architecture/Arm/ArchitectureArm.cpp | 186 ++++++++++++++++++ .../Architecture/Arm/ArchitectureArm.h | 5 + .../ObjectFile/JSON/ObjectFileJSON.cpp | 35 ++++ .../Plugins/ObjectFile/JSON/ObjectFileJSON.h | 3 + .../Process/mach-core/ProcessMachCore.cpp | 17 ++ lldb/source/Target/RegisterContextUnwind.cpp | 44 +++++ .../unwind/cortex-m-exception/Makefile | 1 + .../TestCortexMExceptionUnwind.py | 50 +++++ .../armv7m-nofpu-exception.yaml | 64 ++++++ .../unwind/cortex-m-exception/binary.json | 41 ++++ 14 files changed, 488 insertions(+), 21 deletions(-) create mode 100644 lldb/test/API/functionalities/unwind/cortex-m-exception/Makefile create mode 100644 lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py create mode 100644 lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml create mode 100644 lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json diff --git a/lldb/include/lldb/Core/Architecture.h b/lldb/include/lldb/Core/Architecture.h index b6fc1a20e1e69..ed64a895717a1 100644 --- a/lldb/include/lldb/Core/Architecture.h +++ b/lldb/include/lldb/Core/Architecture.h @@ -12,6 +12,7 @@ #include "lldb/Core/PluginInterface.h" #include "lldb/Target/DynamicRegisterInfo.h" #include "lldb/Target/MemoryTagManager.h" +#include "lldb/Target/RegisterContextUnwind.h" namespace lldb_private { @@ -129,6 +130,14 @@ class Architecture : public PluginInterface { RegisterContext ®_context) const { return false; } + + /// Return an UnwindPlan that allows architecture-defined rules for finding + /// saved registers, given a particular set of register values. + virtual lldb::UnwindPlanSP GetArchitectureUnwindPlan( + lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx, + std::shared_ptr current_unwindplan) { + return lldb::UnwindPlanSP(); + } }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/RegisterContextUnwind.h b/lldb/include/lldb/Target/RegisterContextUnwind.h index b10a364823b83..52c28fd76da98 100644 --- a/lldb/include/lldb/Target/RegisterContextUnwind.h +++ b/lldb/include/lldb/Target/RegisterContextUnwind.h @@ -21,6 +21,7 @@ namespace lldb_private { class UnwindLLDB; +class ArchitectureArm; class RegisterContextUnwind : public lldb_private::RegisterContext { public: @@ -72,6 +73,25 @@ class RegisterContextUnwind : public lldb_private::RegisterContext { // above asynchronous trap handlers (sigtramp) for instance. bool BehavesLikeZerothFrame() const override; +protected: + // Provide a location for where THIS function saved the CALLER's register + // value, or a frame "below" this one saved it. That is, this function doesn't + // modify the register, it may call a function that does & saved it to stack. + // + // The ConcreteRegisterLocation type may be set to eRegisterNotAvailable -- + // this will happen for a volatile register being queried mid-stack. Instead + // of floating frame 0's contents of that register up the stack (which may or + // may not be the value of that reg when the function was executing), we won't + // return any value. + // + // If a non-volatile register (a "preserved" register, a callee-preserved + // register) is requested mid-stack, and no frames "below" the requested stack + // have saved the register anywhere, it is safe to assume that frame 0's + // register value is the same. + lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister( + uint32_t lldb_regnum, + lldb_private::UnwindLLDB::ConcreteRegisterLocation ®loc); + private: enum FrameType { eNormalFrame, @@ -86,6 +106,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext { // UnwindLLDB needs to pass around references to ConcreteRegisterLocations friend class UnwindLLDB; + // Architecture may need to retrieve caller register values from this frame + friend class ArchitectureArm; // Returns true if we have an unwind loop -- the same stack frame unwinding // multiple times. @@ -130,27 +152,6 @@ class RegisterContextUnwind : public lldb_private::RegisterContext { void PropagateTrapHandlerFlagFromUnwindPlan( std::shared_ptr unwind_plan); - // Provide a location for where THIS function saved the CALLER's register - // value - // Or a frame "below" this one saved it, i.e. a function called by this one, - // preserved a register that this - // function didn't modify/use. - // - // The ConcreteRegisterLocation type may be set to eRegisterNotAvailable -- - // this will happen for a volatile register being queried mid-stack. Instead - // of floating frame 0's contents of that register up the stack (which may or - // may not be the value of that reg when the function was executing), we won't - // return any value. - // - // If a non-volatile register (a "preserved" register) is requested mid-stack - // and no frames "below" the requested - // stack have saved the register anywhere, it is safe to assume that frame 0's - // register values are still the same - // as the requesting frame's. - lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister( - uint32_t lldb_regnum, - lldb_private::UnwindLLDB::ConcreteRegisterLocation ®loc); - std::optional GetAbstractRegisterLocation(uint32_t lldb_regnum, lldb::RegisterKind &kind); @@ -202,6 +203,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext { std::shared_ptr GetFullUnwindPlanForFrame(); + lldb::UnwindPlanSP TryAdoptArchitectureUnwindPlan(); + void UnwindLogMsg(const char *fmt, ...) __attribute__((format(printf, 2, 3))); void UnwindLogMsgVerbose(const char *fmt, ...) diff --git a/lldb/include/lldb/Target/UnwindLLDB.h b/lldb/include/lldb/Target/UnwindLLDB.h index f2f65e67a7640..a988a7826c26a 100644 --- a/lldb/include/lldb/Target/UnwindLLDB.h +++ b/lldb/include/lldb/Target/UnwindLLDB.h @@ -22,6 +22,7 @@ namespace lldb_private { class RegisterContextUnwind; +class ArchitectureArm; class UnwindLLDB : public lldb_private::Unwind { public: @@ -37,6 +38,7 @@ class UnwindLLDB : public lldb_private::Unwind { protected: friend class lldb_private::RegisterContextUnwind; + friend class lldb_private::ArchitectureArm; /// An UnwindPlan::Row::AbstractRegisterLocation, combined with the register /// context and memory for a specific stop point, is used to create a diff --git a/lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp b/lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp index 2bcb2c0de97ac..bb0c4ba3f1b57 100644 --- a/lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp +++ b/lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp @@ -1921,6 +1921,13 @@ UnwindPlanSP ABISysV_arm::CreateFunctionEntryUnwindPlan() { UnwindPlanSP ABISysV_arm::CreateDefaultUnwindPlan() { // TODO: Handle thumb + // If we had a Target argument, could at least check + // target.GetArchitecture().GetTriple().isArmMClass() + // which is always thumb. + // To handle thumb properly, we'd need to fetch the current + // CPSR state at unwind time to tell if the processor is + // in thumb mode in this stack frame. There's no way to + // express something like that in an UnwindPlan today. uint32_t fp_reg_num = dwarf_r11; uint32_t pc_reg_num = dwarf_pc; diff --git a/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp b/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp index 81c72122cb7e5..721c4bcfe6342 100644 --- a/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp +++ b/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp @@ -9,10 +9,18 @@ #include "Plugins/Architecture/Arm/ArchitectureArm.h" #include "Plugins/Process/Utility/ARMDefines.h" #include "Plugins/Process/Utility/InstructionUtils.h" +#include "Utility/ARM_DWARF_Registers.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Symbol/UnwindPlan.h" +#include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" +#include "lldb/Target/RegisterNumber.h" #include "lldb/Target/Thread.h" +#include "lldb/Target/UnwindLLDB.h" #include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" using namespace lldb_private; using namespace lldb; @@ -150,3 +158,181 @@ addr_t ArchitectureArm::GetOpcodeLoadAddress(addr_t opcode_addr, } return opcode_addr & ~(1ull); } + +// The ARM M-Profile Armv7-M Architecture Reference Manual, +// subsection "B1.5 Armv7-M exception model", see the parts +// describing "Exception entry behavior" and "Exception +// return behavior". +// When an exception happens on this processor, certain registers are +// saved below the stack pointer, the stack pointer is decremented, +// a special value is put in the link register to indicate the +// exception has been taken, and an exception handler function +// is invoked. +// +// Detect that special value in $lr, and if present, add +// unwind rules for the registers that were saved above this +// stack frame's CFA. Overwrite any register locations that +// the current_unwindplan has for these registers; they are +// not correct when we're invoked this way. +UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan( + Thread &thread, RegisterContextUnwind *regctx, + std::shared_ptr current_unwindplan) { + + ProcessSP process_sp = thread.GetProcess(); + if (!process_sp) + return {}; + + const ArchSpec arch = process_sp->GetTarget().GetArchitecture(); + if (!arch.GetTriple().isArmMClass() || arch.GetAddressByteSize() != 4) + return {}; + + // Get the caller's LR value from regctx (the LR value + // at function entry to this function). + RegisterNumber ra_regnum(thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_RA); + uint32_t ra_regnum_lldb = ra_regnum.GetAsKind(eRegisterKindLLDB); + + if (ra_regnum_lldb == LLDB_INVALID_REGNUM) + return {}; + + UnwindLLDB::ConcreteRegisterLocation regloc = {}; + bool got_concrete_location = false; + if (regctx->SavedLocationForRegister(ra_regnum_lldb, regloc) == + UnwindLLDB::RegisterSearchResult::eRegisterFound) { + got_concrete_location = true; + } else { + RegisterNumber pc_regnum(thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + uint32_t pc_regnum_lldb = pc_regnum.GetAsKind(eRegisterKindLLDB); + if (regctx->SavedLocationForRegister(pc_regnum_lldb, regloc) == + UnwindLLDB::RegisterSearchResult::eRegisterFound) + got_concrete_location = true; + } + + if (!got_concrete_location) + return {}; + + addr_t callers_return_address = LLDB_INVALID_ADDRESS; + const RegisterInfo *reg_info = regctx->GetRegisterInfoAtIndex(ra_regnum_lldb); + if (reg_info) { + RegisterValue reg_value; + if (regctx->ReadRegisterValueFromRegisterLocation(regloc, reg_info, + reg_value)) { + callers_return_address = reg_value.GetAsUInt32(); + } + } + + if (callers_return_address == LLDB_INVALID_ADDRESS) + return {}; + + // ARMv7-M ARM says that the LR will be set to + // one of these values when an exception has taken + // place: + // if HaveFPExt() then + // if CurrentMode==Mode_Handler then + // LR = Ones(27):NOT(CONTROL.FPCA):'0001'; + // else + // LR = Ones(27):NOT(CONTROL.FPCA):'1':CONTROL.SPSEL:'01'; + // else + // if CurrentMode==Mode_Handler then + // LR = Ones(28):'0001'; + // else + // LR = Ones(29):CONTROL.SPSEL:'01'; + + // Top 27 bits are set for an exception return. + const uint32_t exception_return = -1U & ~0b11111U; + // Bit4 is 1 if only GPRs were saved. + const uint32_t gprs_only = 0b10000; + // Bit<1:0> are '01'. + const uint32_t lowbits = 0b01; + + if ((callers_return_address & exception_return) != exception_return) + return {}; + if ((callers_return_address & lowbits) != lowbits) + return {}; + + const bool fp_regs_saved = !(callers_return_address & gprs_only); + + const RegisterKind plan_regkind = current_unwindplan->GetRegisterKind(); + UnwindPlanSP new_plan = std::make_shared(plan_regkind); + new_plan->SetSourceName("Arm Cortex-M exception return UnwindPlan"); + new_plan->SetSourcedFromCompiler(eLazyBoolNo); + new_plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes); + new_plan->SetUnwindPlanForSignalTrap(eLazyBoolYes); + + int stored_regs_size = fp_regs_saved ? 0x68 : 0x20; + + uint32_t gpr_regs[] = {dwarf_r0, dwarf_r1, dwarf_r2, dwarf_r3, + dwarf_r12, dwarf_lr, dwarf_pc, dwarf_cpsr}; + const int gpr_reg_count = std::size(gpr_regs); + uint32_t fpr_regs[] = {dwarf_s0, dwarf_s1, dwarf_s2, dwarf_s3, + dwarf_s4, dwarf_s5, dwarf_s6, dwarf_s7, + dwarf_s8, dwarf_s9, dwarf_s10, dwarf_s11, + dwarf_s12, dwarf_s13, dwarf_s14, dwarf_s15}; + const int fpr_reg_count = std::size(fpr_regs); + + RegisterContextSP reg_ctx_sp = thread.GetRegisterContext(); + std::vector saved_regs; + for (int i = 0; i < gpr_reg_count; i++) { + uint32_t regno = gpr_regs[i]; + reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, gpr_regs[i], + plan_regkind, regno); + saved_regs.push_back(regno); + } + if (fp_regs_saved) { + for (int i = 0; i < fpr_reg_count; i++) { + uint32_t regno = fpr_regs[i]; + reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, fpr_regs[i], + plan_regkind, regno); + saved_regs.push_back(regno); + } + } + + addr_t cfa; + if (!regctx->GetCFA(cfa)) + return {}; + + // The CPSR value saved to stack is actually (from Armv7-M ARM) + // "XPSR<31:10>:frameptralign:XPSR<8:0>" + // Bit 9 indicates that the stack pointer was aligned (to + // an 8-byte alignment) when the exception happened, and we must + // account for that when restoring the original stack pointer value. + Status error; + uint32_t callers_xPSR = + process_sp->ReadUnsignedIntegerFromMemory(cfa + 0x1c, 4, 0, error); + const bool align_stack = callers_xPSR & (1U << 9); + uint32_t callers_sp = cfa + stored_regs_size; + if (align_stack) + callers_sp |= 4; + + Log *log = GetLog(LLDBLog::Unwind); + LLDB_LOGF(log, + "ArchitectureArm::GetArchitectureUnwindPlan found caller return " + "addr of 0x%" PRIx64 ", for frame with CFA 0x%" PRIx64 + ", fp_regs_saved %d, stored_regs_size 0x%x, align stack %d", + callers_return_address, cfa, fp_regs_saved, stored_regs_size, + align_stack); + + uint32_t sp_regnum = dwarf_sp; + reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, dwarf_sp, + plan_regkind, sp_regnum); + + const int row_count = current_unwindplan->GetRowCount(); + for (int i = 0; i < row_count; i++) { + UnwindPlan::Row row = *current_unwindplan->GetRowAtIndex(i); + uint32_t offset = 0; + const size_t saved_reg_count = saved_regs.size(); + for (size_t j = 0; j < saved_reg_count; j++) { + // The locations could be set with + // SetRegisterLocationToIsConstant(regno, cfa+offset) + // expressing it in terms of CFA addr+offset - this UnwindPlan + // is only used once, with this specific CFA. I'm not sure + // which will be clearer for someone reading the unwind log. + row.SetRegisterLocationToAtCFAPlusOffset(saved_regs[j], offset, true); + offset += 4; + } + row.SetRegisterLocationToIsCFAPlusOffset(sp_regnum, callers_sp - cfa, true); + new_plan->AppendRow(row); + } + return new_plan; +} diff --git a/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h b/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h index f579d6b625051..52277dc5dbae0 100644 --- a/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h +++ b/lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h @@ -10,6 +10,7 @@ #define LLDB_SOURCE_PLUGINS_ARCHITECTURE_ARM_ARCHITECTUREARM_H #include "lldb/Core/Architecture.h" +#include "lldb/Target/Thread.h" namespace lldb_private { @@ -29,6 +30,10 @@ class ArchitectureArm : public Architecture { lldb::addr_t GetOpcodeLoadAddress(lldb::addr_t load_addr, AddressClass addr_class) const override; + lldb::UnwindPlanSP GetArchitectureUnwindPlan( + lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx, + std::shared_ptr current_unwindplan) override; + private: static std::unique_ptr Create(const ArchSpec &arch); ArchitectureArm() = default; diff --git a/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.cpp b/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.cpp index cb8ba05d461d4..69885aa53ca30 100644 --- a/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.cpp +++ b/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.cpp @@ -12,6 +12,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Target.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "llvm/ADT/DenseSet.h" @@ -233,6 +234,40 @@ void ObjectFileJSON::CreateSections(SectionList &unified_section_list) { } } +bool ObjectFileJSON::SetLoadAddress(Target &target, lldb::addr_t value, + bool value_is_offset) { + Log *log(GetLog(LLDBLog::DynamicLoader)); + if (!m_sections_up) + return true; + + addr_t slide = value; + if (!value_is_offset) { + addr_t lowest_addr = LLDB_INVALID_ADDRESS; + for (const SectionSP §ion_sp : *m_sections_up) { + addr_t section_load_addr = section_sp->GetFileAddress(); + lowest_addr = std::min(lowest_addr, section_load_addr); + } + if (lowest_addr == LLDB_INVALID_ADDRESS) + return false; + slide = value - lowest_addr; + } + + // Apply slide to each section's file address. + for (const SectionSP §ion_sp : *m_sections_up) { + addr_t section_load_addr = section_sp->GetFileAddress(); + if (section_load_addr != LLDB_INVALID_ADDRESS) { + LLDB_LOGF( + log, + "ObjectFileJSON::SetLoadAddress section %s to load addr 0x%" PRIx64, + section_sp->GetName().AsCString(), section_load_addr + slide); + target.SetSectionLoadAddress(section_sp, section_load_addr + slide, + /*warn_multiple=*/true); + } + } + + return true; +} + bool ObjectFileJSON::MagicBytesMatch(DataBufferSP data_sp, lldb::addr_t data_offset, lldb::addr_t data_length) { diff --git a/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.h b/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.h index b72565f468862..029c8ff188934 100644 --- a/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.h +++ b/lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.h @@ -86,6 +86,9 @@ class ObjectFileJSON : public ObjectFile { Strata CalculateStrata() override { return eStrataUser; } + bool SetLoadAddress(Target &target, lldb::addr_t value, + bool value_is_offset) override; + static bool MagicBytesMatch(lldb::DataBufferSP data_sp, lldb::addr_t offset, lldb::addr_t length); diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp index 6037c8d2514b8..a780b3f59aded 100644 --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -799,6 +799,23 @@ Status ProcessMachCore::DoGetMemoryRegionInfo(addr_t load_addr, region_info.SetMapped(MemoryRegionInfo::eNo); } return Status(); + } else { + // The corefile has no LC_SEGMENT at this virtual address, + // but see if there is a binary whose Section has been + // loaded at that address in the current Target. + Address addr; + if (GetTarget().ResolveLoadAddress(load_addr, addr)) { + SectionSP section_sp(addr.GetSection()); + if (section_sp) { + region_info.GetRange().SetRangeBase( + section_sp->GetLoadBaseAddress(&GetTarget())); + region_info.GetRange().SetByteSize(section_sp->GetByteSize()); + if (region_info.GetRange().Contains(load_addr)) { + region_info.SetLLDBPermissions(section_sp->GetPermissions()); + return Status(); + } + } + } } region_info.GetRange().SetRangeBase(load_addr); diff --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp index 880300d0637fb..bb049b0cffca6 100644 --- a/lldb/source/Target/RegisterContextUnwind.cpp +++ b/lldb/source/Target/RegisterContextUnwind.cpp @@ -293,6 +293,9 @@ void RegisterContextUnwind::InitializeZerothFrame() { return; } + // Give the Architecture a chance to replace the UnwindPlan. + TryAdoptArchitectureUnwindPlan(); + UnwindLogMsg("initialized frame current pc is 0x%" PRIx64 " cfa is 0x%" PRIx64 " afa is 0x%" PRIx64 " using %s UnwindPlan", (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), @@ -482,6 +485,9 @@ void RegisterContextUnwind::InitializeNonZerothFrame() { } } + // Give the Architecture a chance to replace the UnwindPlan. + TryAdoptArchitectureUnwindPlan(); + UnwindLogMsg("initialized frame cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, (uint64_t)m_cfa, (uint64_t)m_afa); return; @@ -686,6 +692,9 @@ void RegisterContextUnwind::InitializeNonZerothFrame() { } } + // Give the Architecture a chance to replace the UnwindPlan. + TryAdoptArchitectureUnwindPlan(); + UnwindLogMsg("initialized frame current pc is 0x%" PRIx64 " cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), @@ -1695,6 +1704,41 @@ RegisterContextUnwind::SavedLocationForRegister( return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; } +UnwindPlanSP RegisterContextUnwind::TryAdoptArchitectureUnwindPlan() { + if (!m_full_unwind_plan_sp) + return {}; + ProcessSP process_sp = m_thread.GetProcess(); + if (!process_sp) + return {}; + + UnwindPlanSP arch_override_plan_sp; + if (Architecture *arch = process_sp->GetTarget().GetArchitecturePlugin()) + arch_override_plan_sp = + arch->GetArchitectureUnwindPlan(m_thread, this, m_full_unwind_plan_sp); + + if (arch_override_plan_sp) { + m_full_unwind_plan_sp = arch_override_plan_sp; + PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp); + m_registers.clear(); + if (GetLog(LLDBLog::Unwind)) { + UnwindLogMsg( + "Replacing Full Unwindplan with Architecture UnwindPlan, '%s'", + m_full_unwind_plan_sp->GetSourceName().AsCString()); + const UnwindPlan::Row *active_row = + m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + if (active_row) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), + &m_thread, + m_start_pc.GetLoadAddress(&process_sp->GetTarget())); + UnwindLogMsg("%s", active_row_strm.GetData()); + } + } + } + + return {}; +} + // TryFallbackUnwindPlan() -- this method is a little tricky. // // When this is called, the frame above -- the caller frame, the "previous" diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/Makefile b/lldb/test/API/functionalities/unwind/cortex-m-exception/Makefile new file mode 100644 index 0000000000000..22f1051530f87 --- /dev/null +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/Makefile @@ -0,0 +1 @@ +include Makefile.rules diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py new file mode 100644 index 0000000000000..7647409e80447 --- /dev/null +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/TestCortexMExceptionUnwind.py @@ -0,0 +1,50 @@ +""" +Test that we can backtrace up an ARM Cortex-M Exception return stack +""" + +import lldb +import json +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestCortexMExceptionUnwind(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_no_fpu(self): + """Test that we can backtrace correctly through an ARM Cortex-M Exception return stack""" + + target = self.dbg.CreateTarget("") + exe = "binary.json" + with open(exe) as f: + exe_json = json.load(f) + exe_uuid = exe_json["uuid"] + + target.AddModule(exe, "", exe_uuid) + self.assertTrue(target.IsValid()) + + core = self.getBuildArtifact("core") + self.yaml2macho_core("armv7m-nofpu-exception.yaml", core, exe_uuid) + + process = target.LoadCore(core) + self.assertTrue(process.IsValid()) + + if self.TraceOn(): + self.runCmd("image list") + self.runCmd("target modules dump sections") + self.runCmd("target modules dump symtab") + self.runCmd("bt") + + thread = process.GetThreadAtIndex(0) + self.assertTrue(thread.IsValid()) + + self.assertEqual(thread.GetNumFrames(), 6) + stackframe_names = [ + "exception_catcher", + "exception_catcher", + "exception_thrower", + "main", + ] + for i, name in enumerate(stackframe_names): + self.assertEqual(name, thread.GetFrameAtIndex(i).GetSymbol().GetName()) diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml b/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml new file mode 100644 index 0000000000000..9ce5ff49d9b6e --- /dev/null +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/armv7m-nofpu-exception.yaml @@ -0,0 +1,64 @@ +cpu: armv7m +threads: + - regsets: + - flavor: gpr + registers: [{name: sp, value: 0x2000fe70}, {name: r7, value: 0x2000fe80}, + {name: pc, value: 0x0020392c}, {name: lr, value: 0x0020392d}] +memory-regions: + # stack memory fetched via + # (lldb) p/x $sp + # (lldb) x/128wx $sp + # % pbpaste | sed -e 's,.*: ,,' -e 's/ /, /g' -e 's/$/,/' + - addr: 0x2000fe70 + UInt32: [ + 0x0000002a, 0x20010e58, 0x00203923, 0x00000001, + 0x2000fe88, 0x00203911, 0x2000ffdc, 0xfffffff9, + 0x00000102, 0x00000002, 0x000003f0, 0x0000002a, + 0x20012620, 0x00203215, 0x00203366, 0x81000200, + 0x00203215, 0x200128b0, 0x0024928d, 0x2000fecc, + 0x002491ed, 0x20010e58, 0x20010e4c, 0x2000ffa0, + 0x200107a0, 0x0000003c, 0x200116e8, 0x200108b0, + 0x0020b895, 0x00000000, 0x0000e200, 0x2001227d, + 0x200121fd, 0x0000e000, 0x00000000, 0x200129a0, + 0x002035bf, 0x00000029, 0x000003d8, 0x20011120, + 0x200116e0, 0x40003800, 0x20011120, 0x00000000, + 0x00205169, 0x00203713, 0x00000000, 0x0022dcb9, + 0x40003800, 0x20011240, 0x00000000, 0xf7d71ecf, + 0xfc7676d6, 0x00000000, 0x968782d3, 0xe75afbbb, + 0x600d77c8, 0xc1c05886, 0x17f3e76d, 0xefc3054d, + 0x11940aaa, 0x00000000, 0x93bffabb, 0x6db85af0, + 0x00000000, 0x2001d76f, 0xcb35f653, 0x00000000, + 0x00000000, 0x079d5058, 0x00000000, 0x00000000, + 0xc5622949, 0x68682572, 0x00000075, 0x0000e500, + 0x20012c30, 0x00000000, 0xcdfcd8c2, 0x76efc90f, + 0x0024495f, 0x20012bf0, 0x0000e400, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0029089c, 0x0029089c, 0x00000000, 0x2000ffe4, + 0x00202a87, 0x2000ffec, 0x00200257, 0x2000fff4, + 0x00200211, 0x00000000, 0x00000000, 0x7badb3f6, + 0x20010794, 0x20010fac, 0x200109b0, 0x002887a4, + 0x00285688, 0x002854c8, 0x00288f74, 0x0028a618, + 0x0028a6f8, 0x00000000, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x002037dd, 0x00000000, + 0x00000002, 0x00000100, 0x00000000, 0x20010064, + 0x00000000, 0x00000000, 0x00000000, 0x200109c0, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + ] + # exception_catcher() function bytes + # (lldb) dis + # binary`exception_catcher: + # 0x203910 <+0>: push {r3, r4, r5, r6, r7, lr} + # 0x203912 <+2>: add r7, sp, #0x10 + # ... + # (lldb) x/44bx 0x203910 + # % pbpaste | sed -e 's,.*: ,,' -e 's/ /, /g' -e 's/$/,/' + - addr: 0x203910 + UInt8: [ + 0xf8, 0xb5, 0x04, 0xaf, 0x06, 0x4c, 0x07, 0x49, + 0x74, 0xf0, 0x2e, 0xf8, 0x01, 0xac, 0x74, 0xf0, + 0x61, 0xf8, 0x05, 0x48, 0x76, 0xf0, 0xdf, 0xfe, + 0x74, 0xf0, 0x0b, 0xf9, 0xfe, 0xe7, 0x00, 0xbf, + 0x4c, 0x0e, 0x01, 0x20, 0x0d, 0x35, 0x20, 0x00, + 0x98, 0xae, 0x28, 0x00 + ] + diff --git a/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json b/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json new file mode 100644 index 0000000000000..8fcd5307ff82a --- /dev/null +++ b/lldb/test/API/functionalities/unwind/cortex-m-exception/binary.json @@ -0,0 +1,41 @@ +{ + "triple": "armv7m-apple", + "uuid": "2D157DBA-53C9-3AC7-B5A1-9D336EC831CB", + "type": "executable", + "sections": [ + { + "user_id": 100, + "name": "TEXT", + "type": "code", + "address": 2097664, + "size": 598872, + "file_offset": 0, + "file_size": 598872, + "alignment": 2, + "flags": 514, + "read": true, + "write": false, + "execute": true + } + ], + "symbols": [ + { + "name": "main", + "type": "code", + "size": 10, + "address": 2108030 + }, + { + "name": "exception_catcher", + "type": "code", + "size": 44, + "address": 2111760 + }, + { + "name": "exception_thrower", + "type": "code", + "size": 2652, + "address": 2108040 + } + ] +}