[Lldb-commits] [lldb] [lldb][RISCV] Implement trap handler unwind plan (PR #166531)
Jason Molenda via lldb-commits
lldb-commits at lists.llvm.org
Tue Dec 2 18:40:51 PST 2025
================
@@ -302,12 +304,56 @@ static lldb::UnwindPlanSP GetAArch64TrapHandlerUnwindPlan(ConstString name) {
return unwind_plan_sp;
}
+static lldb::UnwindPlanSP GetRISCVTrapHandlerUnwindPlan(ConstString name) {
+ if (name != "__vdso_rt_sigreturn")
+ return UnwindPlanSP{};
+
+ UnwindPlan::Row row;
+
+ // In the signal trampoline frame, sp points to an rt_sigframe[1], which is:
+ // - 128-byte siginfo struct
+ // - ucontext struct:
+ // - 8-byte long (uc_flags)
+ // - 8-byte pointer (*uc_link)
+ // - 24-byte struct (uc_stack)
+ // - 8-byte struct (uc_sigmask)
+ // - 120-byte of padding to allow sigset_t to be expanded in the future
+ // - 8 bytes of padding because sigcontext has 16-byte alignment
+ // - struct sigcontext uc_mcontext
+ // [1]
+ // https://github.com/torvalds/linux/blob/master/arch/riscv/kernel/signal.c
+
+ constexpr size_t siginfo_size = 128;
+ constexpr size_t uc_flags_size = 8;
+ constexpr size_t uc_link_ptr_size = 8;
+ constexpr size_t uc_stack_size = 24;
+ constexpr size_t uc_sigmask_size = 8;
+ constexpr size_t padding_size = 128;
+
+ constexpr size_t offset = siginfo_size + uc_flags_size + uc_link_ptr_size +
+ uc_stack_size + uc_sigmask_size + padding_size;
+
+ row.GetCFAValue().SetIsRegisterPlusOffset(gpr_sp_riscv, offset);
+ for (uint32_t reg_num = 0; reg_num <= 31; ++reg_num)
+ row.SetRegisterLocationToAtCFAPlusOffset(reg_num, reg_num * 8, false);
+
+ UnwindPlanSP unwind_plan_sp = std::make_shared<UnwindPlan>(eRegisterKindLLDB);
+ unwind_plan_sp->AppendRow(std::move(row));
+ unwind_plan_sp->SetSourceName("RISC-V Linux sigcontext");
+ unwind_plan_sp->SetSourcedFromCompiler(eLazyBoolYes);
----------------
jasonmolenda wrote:
@DavidSpickett FWIW my thinking on this was never super clear, sorry it's a bit confusing.
When the unwinder has a variety of unwind plans to choose from, it needs to decide which might be preferable over another that could also be used in a given context. We might have an assembly instruction unwind plan and a compiler generated eh_frame or dwarf debug_frame unwind plan. All things being equal, the compiler generated unwind plan is the better choice.
A secondary, and trickier issue, is that unwind information can be "asynchronous" -- valid at very instruction, including the middle of prologues and epilogues, or "synchronous" -- valid only in the body of the function, typically at places where it may throw an exception, or an exception may need to be passed up from a callee. Some unwind sources like arm index or Darwin's compact unwind are purely synchronous, they only describe the unwind state in the body of the function, where an exception could be thrown. eh_frame and debug_frame are a bit annoying here because they have no indication whether they are asynchronous or synchronous, but because gdb relies on eh_frame exclusively on Linux, the compilers usually generate asynchronous-valid eh_frame instructions.
We only need to worry about this latter issue of asynchronous/synchronous when we are unwinding from the currently-executing function. This is either frame 0, or a frame that faulted/excepted into a fault/exception handler (it did not execute a proper function call).
I doubt the SourcedFromCompiler is actually doing anything important here, but if this were a non-zero-stack frame and the unwinder was trying to choose between an instruction emulation unwind plan and this unwind plan, we definitely want to use this unwind plan. So I probably set it to eLazyBoolYes for that reason.
https://github.com/llvm/llvm-project/pull/166531
More information about the lldb-commits
mailing list