Author: Jonas Devlieghere Date: 2026-07-31T21:44:20-07:00 New Revision: 56eb9ab86741206baacefb62a40c8a54b4382e91
URL: https://github.com/llvm/llvm-project/commit/56eb9ab86741206baacefb62a40c8a54b4382e91 DIFF: https://github.com/llvm/llvm-project/commit/56eb9ab86741206baacefb62a40c8a54b4382e91.diff LOG: [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. Added: Modified: lldb/source/Plugins/Process/wasm/UnwindWasm.cpp lldb/source/Plugins/Process/wasm/UnwindWasm.h lldb/test/API/functionalities/gdb_remote_client/TestWasm.py Removed: ################################################################################ diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp index 39b784315a9c1..df3b146efca14 100644 --- a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp +++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp @@ -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); diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.h b/lldb/source/Plugins/Process/wasm/UnwindWasm.h index ff5e06d23d960..03c7f9643f1f4 100644 --- a/lldb/source/Plugins/Process/wasm/UnwindWasm.h +++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.h @@ -38,6 +38,10 @@ class UnwindWasm : public lldb_private::Unwind { 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; diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py index 861691f5b3ddc..114ddd7adce5a 100644 --- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py +++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py @@ -406,6 +406,45 @@ def test_simple_wasm_debugging_session(self): 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): _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
