[Lldb-commits] [lldb] [lldb] Enforce that ArchDefaultUnwindPlans declare other regs unfetch (PR #203684)
via lldb-commits
lldb-commits at lists.llvm.org
Sat Jun 13 00:48:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v
Author: Jason Molenda (jasonmolenda)
<details>
<summary>Changes</summary>
I noticed a few of the ABI plugins were not setting UnspecifiedRegistersAreUndefined()==true in their UnwindPlan rows. This prevents the unwind engine from trying to fetch registers from any newer stack frames.
The arch default unwind plans are used by lldb when it doesn't have any accurate information about the registers a function has spilled to stack, or where -- it only knows enough to continue walking the stack, assuming all the functions have stored the caller's pc & fp to stack. It is the last-ditch fallback when we backtrace through no-symbol / no-unwind info code.
Normally when the unwind engine does not see a non-volatile aka callee-preserved register mentioned in an UnwindPlan, that means this function did not save the register to stack, it left the reg contents unmodified. So if we are looking for x12 in frame 3, and frame 2 doesn't mention x12 in its UnwindPlan, we will look in frame 1 to see if it spilled x12 to stack. If not, then we look at frame 0 and if it also hasn't spilled it, we copy the x12 value from the live register context and use it in frame 3.
Arch default unwind plans are dangerous if they allow this to happen because we don't know if a stack frame saved a register to stack and then changed the value in the register. It's unsafe for us to make any assumptions about any register not explicitly listed in the UnwindPlan.
I added debug-build asserts for all uses of an ArchDefaultUnwindPlan to enforce this and catch any future introductions of the mistake. Very easy to do unintentionally in new ABIs.
---
Full diff: https://github.com/llvm/llvm-project/pull/203684.diff
7 Files Affected:
- (modified) lldb/include/lldb/Symbol/UnwindPlan.h (+1-1)
- (modified) lldb/source/Commands/CommandObjectTarget.cpp (+7)
- (modified) lldb/source/Plugins/ABI/LoongArch/ABISysV_loongarch.cpp (+1)
- (modified) lldb/source/Plugins/ABI/MSP430/ABISysV_msp430.cpp (+1)
- (modified) lldb/source/Plugins/ABI/RISCV/ABISysV_riscv.cpp (+1)
- (modified) lldb/source/Symbol/FuncUnwinders.cpp (+10-1)
- (modified) lldb/source/Target/RegisterContextUnwind.cpp (+15)
``````````diff
diff --git a/lldb/include/lldb/Symbol/UnwindPlan.h b/lldb/include/lldb/Symbol/UnwindPlan.h
index fe8081f83c590..99d948f8b8a8c 100644
--- a/lldb/include/lldb/Symbol/UnwindPlan.h
+++ b/lldb/include/lldb/Symbol/UnwindPlan.h
@@ -409,7 +409,7 @@ class UnwindPlan {
m_unspecified_registers_are_undefined = unspec_is_undef;
}
- bool GetUnspecifiedRegistersAreUndefined() {
+ bool GetUnspecifiedRegistersAreUndefined() const {
return m_unspecified_registers_are_undefined;
}
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 56e0dfef34444..49b2c43c52222 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -3786,6 +3786,13 @@ class CommandObjectTargetModulesShowUnwind : public CommandObjectParsed {
ABISP abi_sp = process->GetABI();
if (abi_sp) {
if (UnwindPlanSP plan_sp = abi_sp->CreateDefaultUnwindPlan()) {
+#ifndef NDEBUG
+ if (plan_sp && plan_sp->GetRowCount() > 0)
+ assert(plan_sp->GetRowAtIndex(0)
+ ->GetUnspecifiedRegistersAreUndefined() &&
+ "Default UnwindPlan must set "
+ "UnspecifiedRegistersAreUndefined to true");
+#endif
result.GetOutputStream().Printf("Arch default UnwindPlan:\n");
plan_sp->Dump(result.GetOutputStream(), thread.get(),
LLDB_INVALID_ADDRESS);
diff --git a/lldb/source/Plugins/ABI/LoongArch/ABISysV_loongarch.cpp b/lldb/source/Plugins/ABI/LoongArch/ABISysV_loongarch.cpp
index d2a2cbe0643ac..0c4495be9a7ba 100644
--- a/lldb/source/Plugins/ABI/LoongArch/ABISysV_loongarch.cpp
+++ b/lldb/source/Plugins/ABI/LoongArch/ABISysV_loongarch.cpp
@@ -566,6 +566,7 @@ UnwindPlanSP ABISysV_loongarch::CreateDefaultUnwindPlan() {
// have been spilled to stack already.
row.SetRegisterLocationToAtCFAPlusOffset(fp_reg_num, reg_size * -2, true);
row.SetRegisterLocationToAtCFAPlusOffset(pc_reg_num, reg_size * -1, true);
+ row.SetUnspecifiedRegistersAreUndefined(true);
auto plan_sp = std::make_shared<UnwindPlan>(eRegisterKindGeneric);
plan_sp->AppendRow(std::move(row));
diff --git a/lldb/source/Plugins/ABI/MSP430/ABISysV_msp430.cpp b/lldb/source/Plugins/ABI/MSP430/ABISysV_msp430.cpp
index af7ef03b94c4c..da2c01b4a2ad4 100644
--- a/lldb/source/Plugins/ABI/MSP430/ABISysV_msp430.cpp
+++ b/lldb/source/Plugins/ABI/MSP430/ABISysV_msp430.cpp
@@ -331,6 +331,7 @@ UnwindPlanSP ABISysV_msp430::CreateDefaultUnwindPlan() {
row.SetRegisterLocationToAtCFAPlusOffset(pc_reg_num, -2, true);
row.SetRegisterLocationToIsCFAPlusOffset(sp_reg_num, 0, true);
row.SetRegisterLocationToUnspecified(fp_reg_num, true);
+ row.SetUnspecifiedRegistersAreUndefined(true);
auto plan_sp = std::make_shared<UnwindPlan>(eRegisterKindDWARF);
plan_sp->AppendRow(std::move(row));
diff --git a/lldb/source/Plugins/ABI/RISCV/ABISysV_riscv.cpp b/lldb/source/Plugins/ABI/RISCV/ABISysV_riscv.cpp
index bf0e5a15ad790..f056b7958c063 100644
--- a/lldb/source/Plugins/ABI/RISCV/ABISysV_riscv.cpp
+++ b/lldb/source/Plugins/ABI/RISCV/ABISysV_riscv.cpp
@@ -753,6 +753,7 @@ UnwindPlanSP ABISysV_riscv::CreateDefaultUnwindPlan() {
// have been spilled to stack already.
row.SetRegisterLocationToAtCFAPlusOffset(fp_reg_num, reg_size * -2, true);
row.SetRegisterLocationToAtCFAPlusOffset(pc_reg_num, reg_size * -1, true);
+ row.SetUnspecifiedRegistersAreUndefined(true);
auto plan_sp = std::make_shared<UnwindPlan>(eRegisterKindGeneric);
plan_sp->AppendRow(std::move(row));
diff --git a/lldb/source/Symbol/FuncUnwinders.cpp b/lldb/source/Symbol/FuncUnwinders.cpp
index fffc35d7f6177..c0052fff280c6 100644
--- a/lldb/source/Symbol/FuncUnwinders.cpp
+++ b/lldb/source/Symbol/FuncUnwinders.cpp
@@ -459,8 +459,17 @@ FuncUnwinders::GetUnwindPlanArchitectureDefault(Thread &thread) {
ProcessSP process_sp(thread.CalculateProcess());
if (process_sp) {
- if (ABI *abi = process_sp->GetABI().get())
+ if (ABI *abi = process_sp->GetABI().get()) {
m_unwind_plan_arch_default_sp = abi->CreateDefaultUnwindPlan();
+#ifndef NDEBUG
+ if (m_unwind_plan_arch_default_sp &&
+ m_unwind_plan_arch_default_sp->GetRowCount() > 0)
+ assert(m_unwind_plan_arch_default_sp->GetRowAtIndex(0)
+ ->GetUnspecifiedRegistersAreUndefined() &&
+ "Default UnwindPlan must set UnspecifiedRegistersAreUndefined "
+ "to true");
+#endif
+ }
}
return m_unwind_plan_arch_default_sp;
diff --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp
index 139387db7a04f..fe934cb6c9e97 100644
--- a/lldb/source/Target/RegisterContextUnwind.cpp
+++ b/lldb/source/Target/RegisterContextUnwind.cpp
@@ -442,6 +442,13 @@ void RegisterContextUnwind::InitializeNonZerothFrame() {
if (abi_sp) {
m_fast_unwind_plan_sp.reset();
m_full_unwind_plan_sp = abi_sp->CreateDefaultUnwindPlan();
+#ifndef NDEBUG
+ if (m_full_unwind_plan_sp && m_full_unwind_plan_sp->GetRowCount() > 0)
+ assert(m_full_unwind_plan_sp->GetRowAtIndex(0)
+ ->GetUnspecifiedRegistersAreUndefined() &&
+ "Default UnwindPlan must set UnspecifiedRegistersAreUndefined "
+ "to true");
+#endif
if (m_frame_type != eSkipFrame) // don't override eSkipFrame
{
m_frame_type = eNormalFrame;
@@ -810,6 +817,14 @@ RegisterContextUnwind::GetFullUnwindPlanForFrame() {
ABI *abi = process ? process->GetABI().get() : nullptr;
if (abi) {
arch_default_unwind_plan_sp = abi->CreateDefaultUnwindPlan();
+#ifndef NDEBUG
+ if (arch_default_unwind_plan_sp &&
+ arch_default_unwind_plan_sp->GetRowCount() > 0)
+ assert(arch_default_unwind_plan_sp->GetRowAtIndex(0)
+ ->GetUnspecifiedRegistersAreUndefined() &&
+ "Default UnwindPlan must set UnspecifiedRegistersAreUndefined to "
+ "true");
+#endif
} else {
UNWIND_LOG(
log, "unable to get architectural default UnwindPlan from ABI plugin");
``````````
</details>
https://github.com/llvm/llvm-project/pull/203684
More information about the lldb-commits
mailing list