https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/178823
>From 0de79d93e94a84e10a69f9bf8f8d1ce2801e5ab4 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Fri, 30 Jan 2026 15:57:34 -0800 Subject: [PATCH] [lldb] Fix variable access in old SBFrames after inferior function calls When a user holds an SBFrame reference and then triggers an inferior function call (via expression evaluation or GetExtendedBacktraceThread), variables in that frame become inaccessible with "register fp is not available" errors. This happens because inferior function calls execute through ThreadPlanCallFunction, which calls ClearStackFrames() during cleanup to invalidate the unwinder state. ExecutionContextRef objects in the old SBFrames were tracking StackFrameLists via weak_ptr, which became stale when ClearStackFrames() created new instances. The fix uses stable StackFrameList identifiers that persist across ClearStackFrames(): - ID = 0: Normal unwinder frames (constant across all instances) - ID = sequential counter: Scripted frame provider instances ExecutionContextRef now stores the frame list ID instead of a weak_ptr, allowing it to resolve to the current StackFrameList with fresh unwinder state after an inferior function call completes. The `Thread` object preserves the provider chain configuration (m_provider_chain_ids and m_next_provider_id) across ClearStackFrames() so that recreated StackFrameLists get the same IDs. When providers need to be recreated, GetStackFrameList() rebuilds them from the persisted configuration. Added test validates that variables remain accessible after GetExtendedBacktraceThread triggers an inferior function call to fetch libdispatch Queue Info. rdar://167027676 Signed-off-by: Med Ismail Bennani <[email protected]> (cherry picked from commit 2abd4b85f6c83b5d157fa634f2c964ceb42d6b92) Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/ExecutionContext.h | 11 +- lldb/include/lldb/Target/StackFrame.h | 10 +- lldb/include/lldb/Target/StackFrameList.h | 11 +- lldb/include/lldb/Target/Thread.h | 26 ++- lldb/source/Target/ExecutionContext.cpp | 30 +-- lldb/source/Target/StackFrameList.cpp | 20 +- lldb/source/Target/Thread.cpp | 94 ++++++--- .../macosx/extended-backtrace-api/Makefile | 3 + .../TestExtendedBacktraceAPI.py | 191 ++++++++++++++++++ .../API/macosx/extended-backtrace-api/main.m | 53 +++++ 10 files changed, 375 insertions(+), 74 deletions(-) create mode 100644 lldb/test/API/macosx/extended-backtrace-api/Makefile create mode 100644 lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py create mode 100644 lldb/test/API/macosx/extended-backtrace-api/main.m diff --git a/lldb/include/lldb/Target/ExecutionContext.h b/lldb/include/lldb/Target/ExecutionContext.h index 8637234c4fb95..7fc2c6f408486 100644 --- a/lldb/include/lldb/Target/ExecutionContext.h +++ b/lldb/include/lldb/Target/ExecutionContext.h @@ -270,7 +270,7 @@ class ExecutionContextRef { void ClearFrame() { m_stack_id.Clear(); - m_frame_list_wp.reset(); + m_frame_list_id.reset(); } protected: @@ -283,13 +283,8 @@ class ExecutionContextRef { /// backing object changes StackID m_stack_id; ///< The stack ID that this object refers to in case the ///< backing object changes - mutable lldb::StackFrameListWP - m_frame_list_wp; ///< Weak reference to the - ///< frame list that contains - ///< this frame. If we can create a valid - ///< StackFrameListSP from it, we must use it to resolve - ///< the StackID, otherwise, we should ask the Thread's - ///< StackFrameList. + /// Identifier of the frame list containing the frame. + mutable std::optional<uint64_t> m_frame_list_id; }; /// \class ExecutionContext ExecutionContext.h diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index 46922448d6e59..c822d1eecbbf1 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -542,7 +542,7 @@ class StackFrame : public ExecutionContextScope, virtual lldb::RecognizedStackFrameSP GetRecognizedFrame(); - /// Get the StackFrameList that contains this frame. + /// Get the identifier of the StackFrameList that contains this frame. /// /// Returns the StackFrameList that contains this frame, allowing /// frames to resolve execution contexts without calling @@ -550,9 +550,9 @@ class StackFrame : public ExecutionContextScope, /// during frame provider initialization. /// /// \return - /// The StackFrameList that contains this frame, or nullptr if not set. - virtual lldb::StackFrameListSP GetContainingStackFrameList() const { - return m_frame_list_wp.lock(); + /// The identifier of the containing StackFrameList + uint64_t GetContainingStackFrameListIdentifier() const { + return m_frame_list_id; } protected: @@ -598,8 +598,8 @@ class StackFrame : public ExecutionContextScope, /// be the first address of its function). True for actual frame zero as /// well as any other frame with the same trait. bool m_behaves_like_zeroth_frame; + uint64_t m_frame_list_id = 0; lldb::VariableListSP m_variable_list_sp; - lldb::StackFrameListWP m_frame_list_wp; /// Value objects for each variable in m_variable_list_sp. ValueObjectList m_variable_list_value_objects; std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp; diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index c096fe3ff61a0..eecb2cdd1a90c 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -24,7 +24,7 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> { public: // Constructors and Destructors StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp, - bool show_inline_frames); + bool show_inline_frames, uint64_t provider_id = 0); virtual ~StackFrameList(); @@ -104,6 +104,9 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> { /// Get the thread associated with this frame list. Thread &GetThread() const { return m_thread; } + /// Get the unique identifier for this frame list. + uint64_t GetIdentifier() const { return m_identifier; } + protected: friend class Thread; friend class ScriptedFrameProvider; @@ -212,6 +215,9 @@ class StackFrameList : public std::enable_shared_from_this<StackFrameList> { /// Whether or not to show synthetic (inline) frames. Immutable. const bool m_show_inlined_frames; + /// Unique identifier for this frame list instance. + uint64_t m_identifier = 0; + /// Returns true if fetching frames was interrupted, false otherwise. virtual bool FetchFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt); @@ -244,7 +250,8 @@ class SyntheticStackFrameList : public StackFrameList { SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames, const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames, - lldb::SyntheticFrameProviderSP provider_sp); + lldb::SyntheticFrameProviderSP provider_sp, + uint64_t provider_id); protected: /// Override FetchFramesUpTo to lazily return frames from the provider diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index bc1bec57bee5f..6af91cc0797b6 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -19,6 +19,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/RegisterCheckpoint.h" #include "lldb/Target/StackFrameList.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/CompletionRequest.h" #include "lldb/Utility/Event.h" @@ -26,6 +27,8 @@ #include "lldb/Utility/UnimplementedError.h" #include "lldb/Utility/UserID.h" #include "lldb/lldb-private.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/MemoryBuffer.h" #define LLDB_THREAD_MAX_STOP_EXC_DATA 8 @@ -1297,12 +1300,15 @@ class Thread : public std::enable_shared_from_this<Thread>, lldb::StackFrameListSP GetStackFrameList(); + /// Get a frame list by its unique identifier. + lldb::StackFrameListSP GetFrameListByIdentifier(uint64_t id); + llvm::Error LoadScriptedFrameProvider(const ScriptedFrameProviderDescriptor &descriptor); void ClearScriptedFrameProvider(); - const llvm::SmallVector<lldb::SyntheticFrameProviderSP, 0> & + const llvm::SmallDenseMap<uint64_t, lldb::SyntheticFrameProviderSP, 4> & GetFrameProviders() const { return m_frame_providers; } @@ -1410,8 +1416,22 @@ class Thread : public std::enable_shared_from_this<Thread>, /// The Thread backed by this thread, if any. lldb::ThreadWP m_backed_thread; - /// The Scripted Frame Providers for this thread. - llvm::SmallVector<lldb::SyntheticFrameProviderSP, 0> m_frame_providers; + /// Map from frame list ID to its frame provider. + /// Cleared in ClearStackFrames(), repopulated in GetStackFrameList(). + llvm::SmallDenseMap<uint64_t, lldb::SyntheticFrameProviderSP, 4> + m_frame_providers; + + /// Ordered chain of provider IDs. + /// Persists across ClearStackFrames() to maintain stable provider IDs. + llvm::SmallVector<std::pair<ScriptedFrameProviderDescriptor, uint64_t>, 0> + m_provider_chain_ids; + + /// Map from frame list identifier to frame list weak pointer. + mutable llvm::DenseMap<uint64_t, lldb::StackFrameListWP> m_frame_lists_by_id; + + /// Counter for assigning unique provider IDs. Starts at 1 since 0 is + /// reserved for normal unwinder frames. Persists across ClearStackFrames. + uint64_t m_next_provider_id = 1; private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp index b16ff26266c53..cf1b7b0116149 100644 --- a/lldb/source/Target/ExecutionContext.cpp +++ b/lldb/source/Target/ExecutionContext.cpp @@ -468,10 +468,10 @@ operator=(const ExecutionContext &exe_ctx) { lldb::StackFrameSP frame_sp(exe_ctx.GetFrameSP()); if (frame_sp) { m_stack_id = frame_sp->GetStackID(); - m_frame_list_wp = frame_sp->GetContainingStackFrameList(); + m_frame_list_id = frame_sp->GetContainingStackFrameListIdentifier(); } else { m_stack_id.Clear(); - m_frame_list_wp.reset(); + m_frame_list_id.reset(); } return *this; } @@ -514,7 +514,7 @@ void ExecutionContextRef::SetThreadSP(const lldb::ThreadSP &thread_sp) { void ExecutionContextRef::SetFrameSP(const lldb::StackFrameSP &frame_sp) { if (frame_sp) { m_stack_id = frame_sp->GetStackID(); - m_frame_list_wp = frame_sp->GetContainingStackFrameList(); + m_frame_list_id = frame_sp->GetContainingStackFrameListIdentifier(); SetThreadSP(frame_sp->GetThread()); } else { ClearFrame(); @@ -641,21 +641,23 @@ lldb::ThreadSP ExecutionContextRef::GetThreadSP() const { } lldb::StackFrameSP ExecutionContextRef::GetFrameSP() const { - if (m_stack_id.IsValid()) { - // Try the remembered frame list first to avoid circular dependencies - // during frame provider initialization. - if (auto frame_list_sp = m_frame_list_wp.lock()) { + lldb::ThreadSP thread_sp(GetThreadSP()); + if (!thread_sp || !m_stack_id.IsValid()) + return lldb::StackFrameSP(); + + // Try the remembered frame list first to avoid circular dependencies + // during frame provider initialization. + if (m_frame_list_id) { + if (auto frame_list_sp = + thread_sp->GetFrameListByIdentifier(*m_frame_list_id)) { if (auto frame_sp = frame_list_sp->GetFrameWithStackID(m_stack_id)) return frame_sp; } - - // Fallback: ask the thread, which might re-trigger the frame provider - // initialization. - lldb::ThreadSP thread_sp(GetThreadSP()); - if (thread_sp) - return thread_sp->GetFrameWithStackID(m_stack_id); } - return lldb::StackFrameSP(); + + // Fallback: ask the thread, which might re-trigger the frame provider + // initialization. + return thread_sp->GetFrameWithStackID(m_stack_id); } ExecutionContext diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 1ad269e8783cc..65568becd745b 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -38,12 +38,12 @@ using namespace lldb_private; // StackFrameList constructor StackFrameList::StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp, - bool show_inline_frames) + bool show_inline_frames, uint64_t provider_id) : m_thread(thread), m_prev_frames_sp(prev_frames_sp), m_frames(), m_selected_frame_idx(), m_concrete_frames_fetched(0), m_current_inlined_depth(UINT32_MAX), m_current_inlined_pc(LLDB_INVALID_ADDRESS), - m_show_inlined_frames(show_inline_frames) { + m_show_inlined_frames(show_inline_frames), m_identifier(provider_id) { if (prev_frames_sp) { m_current_inlined_depth = prev_frames_sp->m_current_inlined_depth; m_current_inlined_pc = prev_frames_sp->m_current_inlined_pc; @@ -59,8 +59,8 @@ StackFrameList::~StackFrameList() { SyntheticStackFrameList::SyntheticStackFrameList( Thread &thread, lldb::StackFrameListSP input_frames, const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames, - lldb::SyntheticFrameProviderSP provider_sp) - : StackFrameList(thread, prev_frames_sp, show_inline_frames), + lldb::SyntheticFrameProviderSP provider_sp, uint64_t provider_id) + : StackFrameList(thread, prev_frames_sp, show_inline_frames, provider_id), m_input_frames(std::move(input_frames)), m_provider(std::move(provider_sp)) {} @@ -89,7 +89,7 @@ bool SyntheticStackFrameList::FetchFramesUpTo( GetThread().GetProcess().get()); // Set the frame list weak pointer so ExecutionContextRef can resolve // the frame without calling Thread::GetStackFrameList(). - frame_sp->m_frame_list_wp = shared_from_this(); + frame_sp->m_frame_list_id = GetIdentifier(); m_frames.push_back(frame_sp); } @@ -375,7 +375,7 @@ void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) { m_thread.shared_from_this(), frame_idx, concrete_frame_idx, cfa, cfa_is_valid, pc, StackFrame::Kind::Regular, artificial, behaves_like_zeroth_frame, &sc); - synth_frame->m_frame_list_wp = shared_from_this(); + synth_frame->m_frame_list_id = GetIdentifier(); m_frames.push_back(synth_frame); LLDB_LOG(log, "Pushed frame {0} at {1:x}", callee->GetDisplayName(), pc); } @@ -491,7 +491,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, unwind_frame_sp = std::make_shared<StackFrame>( m_thread.shared_from_this(), m_frames.size(), idx, reg_ctx_sp, cfa, pc, behaves_like_zeroth_frame, nullptr); - unwind_frame_sp->m_frame_list_wp = shared_from_this(); + unwind_frame_sp->m_frame_list_id = GetIdentifier(); m_frames.push_back(unwind_frame_sp); } } else { @@ -526,7 +526,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, // although its concrete index will stay the same. SynthesizeTailCallFrames(*unwind_frame_sp.get()); - unwind_frame_sp->m_frame_list_wp = shared_from_this(); + unwind_frame_sp->m_frame_list_id = GetIdentifier(); m_frames.push_back(unwind_frame_sp); } @@ -551,7 +551,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, unwind_frame_sp->GetRegisterContextSP(), cfa, next_frame_address, behaves_like_zeroth_frame, &next_frame_sc)); - frame_sp->m_frame_list_wp = shared_from_this(); + frame_sp->m_frame_list_id = GetIdentifier(); m_frames.push_back(frame_sp); unwind_sc = next_frame_sc; curr_frame_address = next_frame_address; @@ -608,7 +608,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, prev_frame->UpdatePreviousFrameFromCurrentFrame(*curr_frame); // Now copy the fixed up previous frame into the current frames so the // pointer doesn't change. - prev_frame_sp->m_frame_list_wp = shared_from_this(); + prev_frame_sp->m_frame_list_id = GetIdentifier(); m_frames[curr_frame_idx] = prev_frame_sp; #if defined(DEBUG_STACK_FRAMES) diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 70d8650662348..d3fe09b6cdef7 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -29,7 +29,6 @@ #include "lldb/Target/ScriptedThreadPlan.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" -#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadPlan.h" @@ -263,6 +262,7 @@ void Thread::DestroyThread() { m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); m_frame_providers.clear(); + m_provider_chain_ids.clear(); m_prev_framezero_pc.reset(); } @@ -1465,16 +1465,16 @@ StackFrameListSP Thread::GetStackFrameList() { const auto &descriptors = target.GetScriptedFrameProviderDescriptors(); // Collect all descriptors that apply to this thread. - std::vector<const ScriptedFrameProviderDescriptor *> - applicable_descriptors; + llvm::SmallVector<const ScriptedFrameProviderDescriptor *, 4> + thread_descriptors; for (const auto &entry : descriptors) { const ScriptedFrameProviderDescriptor &descriptor = entry.second; if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) - applicable_descriptors.push_back(&descriptor); + thread_descriptors.push_back(&descriptor); } // Sort by priority (lower number = higher priority). - llvm::sort(applicable_descriptors, + llvm::sort(thread_descriptors, [](const ScriptedFrameProviderDescriptor *a, const ScriptedFrameProviderDescriptor *b) { // nullopt (no priority) sorts last (UINT32_MAX). @@ -1484,7 +1484,7 @@ StackFrameListSP Thread::GetStackFrameList() { }); // Load ALL matching providers in priority order. - for (const auto *descriptor : applicable_descriptors) { + for (const auto *descriptor : thread_descriptors) { if (llvm::Error error = LoadScriptedFrameProvider(*descriptor)) { LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error), "Failed to load scripted frame provider: {0}"); @@ -1495,58 +1495,85 @@ StackFrameListSP Thread::GetStackFrameList() { } // Create the frame list based on whether we have providers. - if (!m_frame_providers.empty()) { + if (!m_provider_chain_ids.empty()) { // We have providers - use the last one in the chain. // The last provider has already been chained with all previous providers. - StackFrameListSP input_frames = m_frame_providers.back()->GetInputFrames(); - m_curr_frames_sp = std::make_shared<SyntheticStackFrameList>( - *this, input_frames, m_prev_frames_sp, true, m_frame_providers.back()); + auto last_desc_id_pair = m_provider_chain_ids.back(); + uint64_t last_id = last_desc_id_pair.second; + auto it = m_frame_providers.find(last_desc_id_pair.second); + if (it != m_frame_providers.end()) { + SyntheticFrameProviderSP last_provider = it->second; + StackFrameListSP input_frames = last_provider->GetInputFrames(); + m_curr_frames_sp = std::make_shared<SyntheticStackFrameList>( + *this, input_frames, m_prev_frames_sp, true, last_provider, last_id); + } } else { - // No provider - use normal unwinder frames. - m_curr_frames_sp = - std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true); + // No provider - use normal unwinder frames with stable ID = 0. + m_curr_frames_sp = std::make_shared<StackFrameList>( + *this, m_prev_frames_sp, true, /*provider_id=*/0); + } + + // Register this frame list by its identifier for later lookup. + if (m_curr_frames_sp) { + m_frame_lists_by_id[m_curr_frames_sp->GetIdentifier()] = m_curr_frames_sp; } return m_curr_frames_sp; } +lldb::StackFrameListSP Thread::GetFrameListByIdentifier(uint64_t id) { + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + + auto it = m_frame_lists_by_id.find(id); + if (it != m_frame_lists_by_id.end()) { + return it->second.lock(); + } + return nullptr; +} + llvm::Error Thread::LoadScriptedFrameProvider( const ScriptedFrameProviderDescriptor &descriptor) { std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); - // Create input frames for this provider: - // - If no providers exist yet, use real unwinder frames. - // - If providers exist, wrap the previous provider in a - // SyntheticStackFrameList. - // This creates the chain: each provider's OUTPUT becomes the next - // provider's INPUT. - StackFrameListSP new_provider_input_frames; + StackFrameListSP input_frames; if (m_frame_providers.empty()) { - // First provider gets real unwinder frames. - new_provider_input_frames = - std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true); + // First provider gets real unwinder frames with stable ID = 0. + input_frames = + std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true, + /*provider_id=*/0); } else { - // Subsequent providers get the previous provider's OUTPUT. - // We create a SyntheticStackFrameList that wraps the previous provider. - SyntheticFrameProviderSP prev_provider = m_frame_providers.back(); + // Subsequent providers wrap the previous provider. + auto prev_provider_desc_id_pair = m_provider_chain_ids.back(); + uint64_t prev_provider_id = prev_provider_desc_id_pair.second; + auto it = m_frame_providers.find(prev_provider_id); + if (it == m_frame_providers.end()) + return llvm::createStringError("Previous frame provider not found"); + SyntheticFrameProviderSP prev_provider = it->second; StackFrameListSP prev_provider_frames = prev_provider->GetInputFrames(); - new_provider_input_frames = std::make_shared<SyntheticStackFrameList>( - *this, prev_provider_frames, m_prev_frames_sp, true, prev_provider); + input_frames = std::make_shared<SyntheticStackFrameList>( + *this, prev_provider_frames, m_prev_frames_sp, true, prev_provider, + prev_provider_id); } - auto provider_or_err = SyntheticFrameProvider::CreateInstance( - new_provider_input_frames, descriptor); + auto provider_or_err = + SyntheticFrameProvider::CreateInstance(input_frames, descriptor); if (!provider_or_err) return provider_or_err.takeError(); - // Append to the chain. - m_frame_providers.push_back(*provider_or_err); + uint64_t provider_id = m_next_provider_id++; + m_frame_providers.insert({provider_id, *provider_or_err}); + + // Add to the provider chain. + m_provider_chain_ids.push_back({descriptor, provider_id}); + return llvm::Error::success(); } void Thread::ClearScriptedFrameProvider() { std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); m_frame_providers.clear(); + m_provider_chain_ids.clear(); + m_next_provider_id = 1; // Reset counter. m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); } @@ -1571,6 +1598,9 @@ void Thread::ClearStackFrames() { m_prev_frames_sp.swap(m_curr_frames_sp); m_curr_frames_sp.reset(); + // Clear the provider instances, but keep the chain configuration + // (m_provider_chain_ids and m_next_provider_id) so provider IDs + // remain stable across ClearStackFrames() calls. m_frame_providers.clear(); m_extended_info.reset(); m_extended_info_fetched = false; diff --git a/lldb/test/API/macosx/extended-backtrace-api/Makefile b/lldb/test/API/macosx/extended-backtrace-api/Makefile new file mode 100644 index 0000000000000..845553d5e3f2f --- /dev/null +++ b/lldb/test/API/macosx/extended-backtrace-api/Makefile @@ -0,0 +1,3 @@ +OBJC_SOURCES := main.m + +include Makefile.rules diff --git a/lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py b/lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py new file mode 100644 index 0000000000000..5325f5786620f --- /dev/null +++ b/lldb/test/API/macosx/extended-backtrace-api/TestExtendedBacktraceAPI.py @@ -0,0 +1,191 @@ +"""Test SBThread.GetExtendedBacktraceThread API with queue debugging.""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestExtendedBacktraceAPI(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessDarwin + @add_test_categories(["objc", "pyapi"]) + def test_extended_backtrace_thread_api(self): + """Test GetExtendedBacktraceThread with queue debugging.""" + self.build() + self.extended_backtrace_thread_api() + + def setUp(self): + TestBase.setUp(self) + self.main_source = "main.m" + + def extended_backtrace_thread_api(self): + """Test SBThread.GetExtendedBacktraceThread API.""" + exe = self.getBuildArtifact("a.out") + + # Get Xcode developer directory path. + # Try DEVELOPER_DIR environment variable first, then fall back to xcode-select. + xcode_dev_path = os.environ.get("DEVELOPER_DIR") + + if not xcode_dev_path: + import subprocess + + try: + xcode_dev_path = ( + subprocess.check_output(["xcode-select", "-p"]) + .decode("utf-8") + .strip() + ) + except subprocess.CalledProcessError: + self.skipTest( + "Could not determine Xcode developer path using xcode-select -p" + ) + + # Check for libBacktraceRecording.dylib. + libbtr_path = os.path.join( + xcode_dev_path, "usr/lib/libBacktraceRecording.dylib" + ) + if not os.path.isfile(libbtr_path): + self.skipTest( + f"Skipped because libBacktraceRecording.dylib is not present at {libbtr_path}" + ) + + if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"): + self.skipTest( + "Skipped because introspection libdispatch dylib is not present." + ) + + # Create target. + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + # Set breakpoint on do_work_level_5. + bp = target.BreakpointCreateByName("do_work_level_5", "a.out") + self.assertTrue(bp, VALID_BREAKPOINT) + self.assertGreater( + bp.GetNumLocations(), 0, "Breakpoint has at least one location" + ) + + # Launch the process with libBacktraceRecording and introspection library. + process = target.LaunchSimple( + [], + [ + f"DYLD_INSERT_LIBRARIES={libbtr_path}", + "DYLD_LIBRARY_PATH=/usr/lib/system/introspection", + ], + self.get_process_working_directory(), + ) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertState(process.GetState(), lldb.eStateStopped) + + # Verify we stopped at the breakpoint. + threads = lldbutil.get_threads_stopped_at_breakpoint(process, bp) + self.assertEqual( + len(threads), 1, "Should have exactly one thread stopped at breakpoint" + ) + + thread = threads[0] + self.assertTrue(thread.IsValid(), "Stopped thread is valid") + + # Call GetNumQueues to ensure queue information is loaded. + num_queues = process.GetNumQueues() + + # Check that we can find the com.apple.main-thread queue. + main_thread_queue_found = False + for i in range(num_queues): + queue = process.GetQueueAtIndex(i) + if queue.GetName() == "com.apple.main-thread": + main_thread_queue_found = True + break + + # Verify we have at least 5 frames. + self.assertGreaterEqual( + thread.GetNumFrames(), + 5, + "Thread should have at least 5 frames in backtrace", + ) + + # Get frame 2 BEFORE calling GetExtendedBacktraceThread. + # This mimics what Xcode does - it has the frame objects ready. + frame2 = thread.GetFrameAtIndex(2) + self.assertTrue(frame2.IsValid(), "Frame 2 is valid") + + # Now test GetExtendedBacktraceThread. + # This is the critical part - getting the extended backtrace calls into + # libBacktraceRecording which does an inferior function call, and this + # invalidates/clears the unwinder state. + extended_thread = thread.GetExtendedBacktraceThread("libdispatch") + + # This should be valid since we injected libBacktraceRecording. + self.assertTrue( + extended_thread.IsValid(), + "Extended backtrace thread for 'libdispatch' should be valid with libBacktraceRecording loaded", + ) + + # The extended thread should have frames. + self.assertGreater( + extended_thread.GetNumFrames(), + 0, + "Extended backtrace thread should have at least one frame", + ) + + # Test frame 2 on the extended backtrace thread. + self.assertGreater( + extended_thread.GetNumFrames(), + 2, + "Extended backtrace thread should have at least 3 frames to access frame 2", + ) + + extended_frame2 = extended_thread.GetFrameAtIndex(2) + self.assertTrue(extended_frame2.IsValid(), "Extended thread frame 2 is valid") + + # NOW try to access variables from frame 2 of the ORIGINAL thread. + # This is the key test - after GetExtendedBacktraceThread() has executed + # an inferior function call, the unwinder state may be invalidated. + # Xcode exhibits this bug where variables show "register fp is not available" + # after extended backtrace retrieval. + variables = frame2.GetVariables(False, True, False, True) + self.assertGreater( + variables.GetSize(), 0, "Frame 2 should have at least one variable" + ) + + # Get the first variable. + first_var = variables.GetValueAtIndex(0) + self.assertTrue(first_var.IsValid(), "First variable in frame 2 is valid") + + # Test all variables in frame 2, like Xcode does. + for i in range(variables.GetSize()): + var = variables.GetValueAtIndex(i) + var_name = var.GetName() + + # Check error state immediately after getting the variable (like Xcode). + error = var.GetError() + if not error.Success(): + error_str = error.GetCString() + self.fail( + f"Variable '{var_name}' has error before GetValue(): {error_str}" + ) + + # Actually get the value string - this triggers register reads and is what + # Xcode's variable inspector does. This should not produce errors like + # "register fp is not available". + value_str = var.GetValue() + self.assertIsNotNone( + value_str, f"Should be able to get value string for '{var_name}'" + ) + + # Check for error strings in the value itself. + if value_str and "not available" in value_str: + self.fail( + f"Variable '{var_name}' value contains 'not available': {value_str}" + ) + + # Verify no error after getting the value. + error_after = var.GetError() + if not error_after.Success(): + error_str_after = error_after.GetCString() + self.fail( + f"Variable '{var_name}' has error after GetValue(): {error_str_after}" + ) diff --git a/lldb/test/API/macosx/extended-backtrace-api/main.m b/lldb/test/API/macosx/extended-backtrace-api/main.m new file mode 100644 index 0000000000000..8f2186845a651 --- /dev/null +++ b/lldb/test/API/macosx/extended-backtrace-api/main.m @@ -0,0 +1,53 @@ +#include <dispatch/dispatch.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +void do_work_level_5(void) { + // Frame 0 will have these variables. + int frame0_var = 100; + const char *frame0_string = "frame_zero"; + float frame0_float = 1.5f; + + // This is where we'll set the breakpoint. + printf("Level 5 work executing\n"); // Break here. + while (1) + sleep(1); +} + +void do_work_level_4(void) { + // Frame 1 will have these variables. + int frame1_var = 200; + const char *frame1_string = "frame_one"; + long frame1_long = 9876543210L; + + do_work_level_5(); +} + +void do_work_level_3(void) { + // Frame 2 will have these variables. + int test_variable = 42; + const char *test_string = "test_value"; + double test_double = 3.14159; + + do_work_level_4(); +} + +void do_work_level_2(void) { do_work_level_3(); } + +void do_work_level_1(void *context) { do_work_level_2(); } + +int main(int argc, const char *argv[]) { + // Create a serial dispatch queue. + dispatch_queue_t worker_queue = + dispatch_queue_create("com.test.worker_queue", DISPATCH_QUEUE_SERIAL); + dispatch_queue_t submitter_queue = + dispatch_queue_create("com.test.submitter_queue", DISPATCH_QUEUE_SERIAL); + + // Submit work from one queue to another to create extended backtrace. + dispatch_async_f(submitter_queue, &worker_queue, do_work_level_1); + + // Keep main thread alive. + dispatch_main(); + return 0; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
