[lldb] Bound a WebAssembly backtrace by the target's maximum depth (#213397)

Only UnwindLLDB honored target.process.thread.max-backtrace-depth, so
UnwindWasm reported every frame the stub sent and a stack that recursed
without end was walked to its end.

Bound the frame count by the depth on every query rather than dropping
the frames that exceed it. A Wasm call stack arrives whole on the first
query, so a depth lowered after that has to still apply, which is when a
user reaches for it. The synthetic call frame addresses stay derived
from the whole stack, so the order of the frames does not depend on the
depth.
This commit is contained in:
Jonas Devlieghere 2026-07-31 21:44:20 -07:00 committed by GitHub
parent 9466be2c87
commit 56eb9ab867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 55 additions and 3 deletions

View file

@ -41,7 +41,7 @@ UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) {
uint32_t UnwindWasm::DoGetFrameCount() {
if (m_unwind_complete)
return m_frames.size();
return GetVisibleFrameCount();
m_unwind_complete = true;
m_frames.clear();
@ -57,7 +57,16 @@ uint32_t UnwindWasm::DoGetFrameCount() {
}
m_frames = *call_stack_pcs;
return m_frames.size();
return GetVisibleFrameCount();
}
uint32_t UnwindWasm::GetVisibleFrameCount() {
// A backtrace goes no deeper than the target asks for, which is what bounds
// the walk of a stack that recurses without end. The depth bounds what a
// caller is told rather than what is kept, because it can be raised after a
// stack has been fetched.
return std::min<uint64_t>(m_frames.size(),
GetThread().GetMaxBacktraceDepth());
}
bool UnwindWasm::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
@ -66,7 +75,7 @@ bool UnwindWasm::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
if (m_frames.size() == 0)
DoGetFrameCount();
if (frame_idx >= m_frames.size())
if (frame_idx >= GetVisibleFrameCount())
return false;
behaves_like_zeroth_frame = (frame_idx == 0);

View file

@ -38,6 +38,10 @@ protected:
DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) override;
private:
/// The number of fetched frames a caller is told about, which the target's
/// maximum backtrace depth bounds.
uint32_t GetVisibleFrameCount();
std::vector<lldb::addr_t> m_frames;
bool m_unwind_complete = false;

View file

@ -406,6 +406,45 @@ class TestWasm(GDBRemoteTestBase):
self.assertTrue(b.IsValid())
self.assertEqual(b.GetValueAsUnsigned(), 2)
@skipIfAsan
@skipIfXmlSupportMissing
def test_max_backtrace_depth(self):
"""Test that a Wasm backtrace stops at the depth the target sets, so
that a stack recursing without end is not walked to its end."""
yaml_path = "simple.yaml"
yaml_base, _ = os.path.splitext(yaml_path)
obj_path = self.getBuildArtifact(yaml_base)
self.yaml2obj(yaml_path, obj_path)
call_stacks = [
WasmCallStack(
[WasmStackFrame(0x019C), WasmStackFrame(0x01E5), WasmStackFrame(0x01FE)]
),
]
self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks)
target = self.dbg.CreateTarget("")
process = self.connect(target, "wasm")
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
thread = process.GetThreadAtIndex(0)
self.assertTrue(thread.IsValid())
self.assertTrue(thread.GetFrameAtIndex(0).IsValid())
# A depth set after a stop still applies to the frames a backtrace has
# not reported yet, which is when a user lowers it.
self.runCmd("settings set target.process.thread.max-backtrace-depth 2")
# The stub reported three frames, and the innermost two are the ones
# kept.
self.assertEqual(2, thread.GetNumFrames())
self.assertEqual(thread.GetFrameAtIndex(0).GetPC(), LOAD_ADDRESS | 0x019C)
self.assertEqual(thread.GetFrameAtIndex(1).GetPC(), LOAD_ADDRESS | 0x01E5)
self.assertFalse(thread.GetFrameAtIndex(2).IsValid())
@skipIfAsan
@skipIfXmlSupportMissing
def test_read_global(self):