-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Description
The mechanical reproduction for this issue as I encountered is somewhat complex, here are the ingredients:
- Windows host, or otherwise ensuring that the "default" unwind plan is wrong for
FW
below. - Stepping into a function without debug info (
FN
) from a function with debug info (FW
). The debug info format is DWARF. - Both functions have correct unwinding information recorded in
.debug_frame
, but there is no.eh_frame
.
Under these very specific conditions, step-in behavior will be wrong (we expect a graceful step-over), with "how wrong" depending on how wrong the default unwind plan is for FN
calling FW
. In my case (wasmtime-transformed DWARF, FN
is a WASM function with WASM-level DWARF, FW
- without), the step-over resulted in returning to the caller of FN
, due to the CFI essentially being computed for FN
instead of FW
.
It is easier to explain the problem by looking through the code responsible for finding the unwind plan in this case. First, we get into InitializeZerothFrame
and fail to get a symbol context:
llvm-project/lldb/source/Target/RegisterContextUnwind.cpp
Lines 163 to 176 in 0383e54
AddressRange addr_range; | |
m_sym_ctx_valid = m_current_pc.ResolveFunctionScope(m_sym_ctx, &addr_range); | |
if (m_sym_ctx.symbol) { | |
UnwindLogMsg("with pc value of 0x%" PRIx64 ", symbol name is '%s'", | |
current_pc, GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); | |
} else if (m_sym_ctx.function) { | |
UnwindLogMsg("with pc value of 0x%" PRIx64 ", function name is '%s'", | |
current_pc, GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); | |
} else { | |
UnwindLogMsg("with pc value of 0x%" PRIx64 | |
", no symbol/function name is known.", | |
current_pc); | |
} |
Then we get into GetFullUnwindPlanForFrame
:
llvm-project/lldb/source/Target/RegisterContextUnwind.cpp
Lines 850 to 878 in 0383e54
FuncUnwindersSP func_unwinders_sp; | |
if (m_sym_ctx_valid) { | |
func_unwinders_sp = | |
pc_module_sp->GetUnwindTable().GetFuncUnwindersContainingAddress( | |
m_current_pc, m_sym_ctx); | |
} | |
// No FuncUnwinders available for this pc (stripped function symbols, lldb | |
// could not augment its function table with another source, like | |
// LC_FUNCTION_STARTS or eh_frame in ObjectFileMachO). See if eh_frame or the | |
// .ARM.exidx tables have unwind information for this address, else fall back | |
// to the architectural default unwind. | |
if (!func_unwinders_sp) { | |
m_frame_type = eNormalFrame; | |
if (!pc_module_sp || !pc_module_sp->GetObjectFile() || | |
!m_current_pc.IsValid()) | |
return arch_default_unwind_plan_sp; | |
// Even with -fomit-frame-pointer, we can try eh_frame to get back on | |
// track. | |
DWARFCallFrameInfo *eh_frame = | |
pc_module_sp->GetUnwindTable().GetEHFrameInfo(); | |
if (eh_frame) { | |
auto unwind_plan_sp = | |
std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); | |
if (eh_frame->GetUnwindPlan(m_current_pc, *unwind_plan_sp)) | |
return unwind_plan_sp; | |
} |
Here, since m_sym_ctx_valid == false
, we don't get to GetFuncUnwindersContainingAddress
, which would find our PC in the .debug_frame
table, and there is no .eh_frame
, so we return the default unwind plan.
Looking a bit more at the code, it seems it would be possible to remove the m_sym_ctx_valid
check, and always ask GetFuncUnwindersContainingAddress
for the unwind plan, since its callees can handle a missing symbol context (by getting the code range from the unwinding sections).