| //===-- StackFrameList.cpp ------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Target/StackFrameList.h" | |
| #include "lldb/Breakpoint/Breakpoint.h" | |
| #include "lldb/Breakpoint/BreakpointLocation.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/SourceManager.h" | |
| #include "lldb/Host/StreamFile.h" | |
| #include "lldb/Symbol/Block.h" | |
| #include "lldb/Symbol/Function.h" | |
| #include "lldb/Symbol/Symbol.h" | |
| #include "lldb/Target/BorrowedStackFrame.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/StackFrame.h" | |
| #include "lldb/Target/StackFrameRecognizer.h" | |
| #include "lldb/Target/StopInfo.h" | |
| #include "lldb/Target/SyntheticFrameProvider.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Target/Unwind.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/Policy.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/Support/ConvertUTF.h" | |
| #include <memory> | |
| //#define DEBUG_STACK_FRAMES 1 | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| // StackFrameList constructor | |
| StackFrameList::StackFrameList(Thread &thread, | |
| const lldb::StackFrameListSP &prev_frames_sp, | |
| bool show_inline_frames, | |
| lldb::frame_list_id_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_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; | |
| } | |
| } | |
| StackFrameList::~StackFrameList() { | |
| // Call clear since this takes a lock and clears the stack frame list in case | |
| // another thread is currently using this stack frame list | |
| Clear(); | |
| } | |
| SyntheticStackFrameList::SyntheticStackFrameList( | |
| Thread &thread, lldb::StackFrameListSP input_frames, | |
| const lldb::StackFrameListSP &prev_frames_sp, bool 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)) {} | |
| bool SyntheticStackFrameList::FetchFramesUpTo( | |
| uint32_t end_idx, InterruptionControl allow_interrupt) { | |
| // Use the provider to generate frames lazily. | |
| if (m_provider) { | |
| // Count how many synthetic frames already exist so we assign unique CFAs | |
| // to new ones. This must not be a local initialized to zero — when | |
| // FetchFramesUpTo is called incrementally (first for a small range, then | |
| // for the full stack), a zero-initialized counter would hand out duplicate | |
| // CFA values, creating StackID collisions for PC-less synthetic frames. | |
| size_t num_synthetic_frames = 0; | |
| for (const auto &f : m_frames) { | |
| if (f && f->IsSynthetic()) | |
| num_synthetic_frames++; | |
| } | |
| // Keep fetching until we reach end_idx or the provider returns an error. | |
| for (uint32_t idx = m_frames.size(); idx <= end_idx; idx++) { | |
| if (allow_interrupt && | |
| m_thread.GetProcess()->GetTarget().GetDebugger().InterruptRequested()) | |
| return true; | |
| // Ensure the provider sees its parent StackFrameList, not the | |
| // synthetic list being constructed. In a chain A->B->C, provider C | |
| // must consult B's output - using its own list would be nonsensical. | |
| // This also applies when the provider runs commands or expressions: | |
| // any path that fetches a StackFrameList should transparently get the | |
| // parent list. As a side benefit, this avoids circular re-entrancy and | |
| // deadlocks on the private state thread. | |
| m_thread.PushProviderFrameList(m_input_frames); | |
| auto clear_active_frames = | |
| llvm::scope_exit([&]() { m_thread.PopProviderFrameList(); }); | |
| auto frame_or_err = m_provider->GetFrameAtIndex(idx); | |
| if (!frame_or_err) { | |
| // Provider returned error - we've reached the end. | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), frame_or_err.takeError(), | |
| "Frame provider reached end at index {1}: {0}", idx); | |
| SetAllFramesFetched(); | |
| break; | |
| } | |
| StackFrameSP frame_sp = *frame_or_err; | |
| // Synthetic frames can provide a CFA. If they haven't, set it to the | |
| // frame index which will at least order the frames on this stop. | |
| if (frame_sp->IsSynthetic() && !frame_sp->GetStackID().IsValid()) | |
| frame_sp->GetStackID().SetCFA(num_synthetic_frames++, | |
| GetThread().GetProcess().get()); | |
| // Set the frame list weak pointer so ExecutionContextRef can resolve | |
| // the frame without calling Thread::GetStackFrameList(). | |
| frame_sp->m_frame_list_id = GetIdentifier(); | |
| m_frames.push_back(frame_sp); | |
| } | |
| return false; // Not interrupted. | |
| } | |
| // If no provider, fall back to the base implementation. | |
| return StackFrameList::FetchFramesUpTo(end_idx, allow_interrupt); | |
| } | |
| void StackFrameList::CalculateCurrentInlinedDepth() { | |
| uint32_t cur_inlined_depth = GetCurrentInlinedDepth(); | |
| if (cur_inlined_depth == UINT32_MAX) { | |
| ResetCurrentInlinedDepth(); | |
| } | |
| } | |
| uint32_t StackFrameList::GetCurrentInlinedDepth() { | |
| std::lock_guard<std::mutex> guard(m_inlined_depth_mutex); | |
| if (m_show_inlined_frames && m_current_inlined_pc != LLDB_INVALID_ADDRESS) { | |
| lldb::addr_t cur_pc = m_thread.GetRegisterContext()->GetPC(); | |
| if (cur_pc != m_current_inlined_pc) { | |
| m_current_inlined_pc = LLDB_INVALID_ADDRESS; | |
| m_current_inlined_depth = UINT32_MAX; | |
| LLDB_LOGF_VERBOSE( | |
| GetLog(LLDBLog::Step), | |
| "GetCurrentInlinedDepth: invalidating current inlined depth.\n"); | |
| } | |
| return m_current_inlined_depth; | |
| } else { | |
| return UINT32_MAX; | |
| } | |
| } | |
| void StackFrameList::ResetCurrentInlinedDepth() { | |
| if (!m_show_inlined_frames) | |
| return; | |
| StopInfoSP stop_info_sp = m_thread.GetStopInfo(); | |
| if (!stop_info_sp) | |
| return; | |
| bool inlined = true; | |
| auto inline_depth = stop_info_sp->GetSuggestedStackFrameIndex(inlined); | |
| // We're only adjusting the inlined stack here. | |
| Log *log = GetLog(LLDBLog::Step); | |
| if (inline_depth) { | |
| std::lock_guard<std::mutex> guard(m_inlined_depth_mutex); | |
| m_current_inlined_depth = *inline_depth; | |
| m_current_inlined_pc = m_thread.GetRegisterContext()->GetPC(); | |
| LLDB_LOGF_VERBOSE(log, | |
| "ResetCurrentInlinedDepth: setting inlined " | |
| "depth: %d 0x%" PRIx64 ".\n", | |
| m_current_inlined_depth, m_current_inlined_pc); | |
| } else { | |
| std::lock_guard<std::mutex> guard(m_inlined_depth_mutex); | |
| m_current_inlined_pc = LLDB_INVALID_ADDRESS; | |
| m_current_inlined_depth = UINT32_MAX; | |
| LLDB_LOGF_VERBOSE( | |
| log, "ResetCurrentInlinedDepth: Invalidating current inlined depth.\n"); | |
| } | |
| } | |
| bool StackFrameList::DecrementCurrentInlinedDepth() { | |
| if (m_show_inlined_frames) { | |
| uint32_t current_inlined_depth = GetCurrentInlinedDepth(); | |
| if (current_inlined_depth != UINT32_MAX) { | |
| if (current_inlined_depth > 0) { | |
| std::lock_guard<std::mutex> guard(m_inlined_depth_mutex); | |
| m_current_inlined_depth--; | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| void StackFrameList::SetCurrentInlinedDepth(uint32_t new_depth) { | |
| std::lock_guard<std::mutex> guard(m_inlined_depth_mutex); | |
| m_current_inlined_depth = new_depth; | |
| if (new_depth == UINT32_MAX) | |
| m_current_inlined_pc = LLDB_INVALID_ADDRESS; | |
| else | |
| m_current_inlined_pc = m_thread.GetRegisterContext()->GetPC(); | |
| } | |
| bool StackFrameList::WereAllFramesFetched() const { | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| return GetAllFramesFetched(); | |
| } | |
| /// A sequence of calls that comprise some portion of a backtrace. Each frame | |
| /// is represented as a pair of a callee and an address within the callee. | |
| struct CallDescriptor { | |
| SymbolContext callee; | |
| CallEdge::AddrType address_type = CallEdge::AddrType::Call; | |
| addr_t address = LLDB_INVALID_ADDRESS; | |
| }; | |
| using CallSequence = std::vector<CallDescriptor>; | |
| /// Find the unique path through the call graph from \p begin (with return PC | |
| /// \p return_pc) to \p end. On success this path is stored into \p path, and | |
| /// on failure \p path is unchanged. | |
| /// This function doesn't currently access StackFrameLists at all, it only looks | |
| /// at the frame set in the ExecutionContext it passes around. | |
| static void FindInterveningFrames(Function &begin, Function &end, | |
| ExecutionContext &exe_ctx, Target &target, | |
| addr_t return_pc, CallSequence &path, | |
| ModuleList &images, Log *log) { | |
| LLDB_LOG_VERBOSE(log, "Finding frames between {0} and {1}, retn-pc={2:x}", | |
| begin.GetDisplayName(), end.GetDisplayName(), return_pc); | |
| // Find a non-tail calling edge with the correct return PC. | |
| if (log) | |
| for (const auto &edge : begin.GetCallEdges()) | |
| LLDB_LOG_VERBOSE(log, | |
| "FindInterveningFrames: found call with retn-PC = {0:x}", | |
| edge->GetReturnPCAddress(begin, target)); | |
| CallEdge *first_edge = begin.GetCallEdgeForReturnAddress(return_pc, target); | |
| if (!first_edge) { | |
| LLDB_LOG_VERBOSE(log, | |
| "No call edge outgoing from {0} with retn-PC == {1:x}", | |
| begin.GetDisplayName(), return_pc); | |
| return; | |
| } | |
| // The first callee may not be resolved, or there may be nothing to fill in. | |
| SymbolContext first_callee = first_edge->GetCallee(images, exe_ctx); | |
| if (!first_callee.function) { | |
| LLDB_LOG_VERBOSE(log, "Could not resolve callee"); | |
| return; | |
| } | |
| if (first_callee.function == &end) { | |
| LLDB_LOG_VERBOSE( | |
| log, "Not searching further, first callee is {0} (retn-PC: {1:x})", | |
| end.GetDisplayName(), return_pc); | |
| return; | |
| } | |
| // Run DFS on the tail-calling edges out of the first callee to find \p end. | |
| // Fully explore the set of functions reachable from the first edge via tail | |
| // calls in order to detect ambiguous executions. | |
| struct DFS { | |
| CallSequence active_path = {}; | |
| CallSequence solution_path = {}; | |
| llvm::SmallPtrSet<Function *, 2> visited_nodes = {}; | |
| bool ambiguous = false; | |
| Function *end; | |
| ModuleList &images; | |
| Target ⌖ | |
| ExecutionContext &context; | |
| DFS(Function *end, ModuleList &images, Target &target, | |
| ExecutionContext &context) | |
| : end(end), images(images), target(target), context(context) {} | |
| void search(CallEdge &first_edge, const SymbolContext &first_callee, | |
| CallSequence &path) { | |
| dfs(first_edge, first_callee); | |
| if (!ambiguous) | |
| path = std::move(solution_path); | |
| } | |
| void dfs(CallEdge ¤t_edge, const SymbolContext &callee) { | |
| // Found a path to the target function. | |
| if (callee.function == end) { | |
| if (solution_path.empty()) | |
| solution_path = active_path; | |
| else | |
| ambiguous = true; | |
| return; | |
| } | |
| // Terminate the search if tail recursion is found, or more generally if | |
| // there's more than one way to reach a target. This errs on the side of | |
| // caution: it conservatively stops searching when some solutions are | |
| // still possible to save time in the average case. | |
| if (!visited_nodes.insert(callee.function).second) { | |
| ambiguous = true; | |
| return; | |
| } | |
| // Search the calls made from this callee. | |
| active_path.push_back(CallDescriptor{callee}); | |
| for (const auto &edge : callee.function->GetTailCallingEdges()) { | |
| SymbolContext next_callee = edge->GetCallee(images, context); | |
| if (!next_callee.function) | |
| continue; | |
| std::tie(active_path.back().address_type, active_path.back().address) = | |
| edge->GetCallerAddress(*callee.function, target); | |
| dfs(*edge, next_callee); | |
| if (ambiguous) | |
| return; | |
| } | |
| active_path.pop_back(); | |
| } | |
| }; | |
| DFS(&end, images, target, exe_ctx).search(*first_edge, first_callee, path); | |
| } | |
| /// Given that \p next_frame will be appended to the frame list, synthesize | |
| /// tail call frames between the current end of the list and \p next_frame. | |
| /// If any frames are added, adjust the frame index of \p next_frame. | |
| /// | |
| /// -------------- | |
| /// | ... | <- Completed frames. | |
| /// -------------- | |
| /// | prev_frame | | |
| /// -------------- | |
| /// | ... | <- Artificial frames inserted here. | |
| /// -------------- | |
| /// | next_frame | | |
| /// -------------- | |
| /// | ... | <- Not-yet-visited frames. | |
| /// -------------- | |
| void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) { | |
| // Cannot synthesize tail call frames when the stack is empty (there is no | |
| // "previous" frame). | |
| if (m_frames.empty()) | |
| return; | |
| TargetSP target_sp = next_frame.CalculateTarget(); | |
| if (!target_sp) | |
| return; | |
| lldb::RegisterContextSP next_reg_ctx_sp = next_frame.GetRegisterContext(); | |
| if (!next_reg_ctx_sp) | |
| return; | |
| Log *log = GetLog(LLDBLog::Step); | |
| StackFrame &prev_frame = *m_frames.back().get(); | |
| // Find the functions prev_frame and next_frame are stopped in. The function | |
| // objects are needed to search the lazy call graph for intervening frames. | |
| Function *prev_func = | |
| prev_frame.GetSymbolContext(eSymbolContextFunction).function; | |
| if (!prev_func) { | |
| LLDB_LOG(log, "SynthesizeTailCallFrames: can't find previous function"); | |
| return; | |
| } | |
| Function *next_func = | |
| next_frame.GetSymbolContext(eSymbolContextFunction).function; | |
| if (!next_func) { | |
| LLDB_LOG(log, "SynthesizeTailCallFrames: can't find next function"); | |
| return; | |
| } | |
| // Try to find the unique sequence of (tail) calls which led from next_frame | |
| // to prev_frame. | |
| CallSequence path; | |
| addr_t return_pc = next_reg_ctx_sp->GetPC(); | |
| Target &target = *target_sp.get(); | |
| ModuleList &images = next_frame.CalculateTarget()->GetImages(); | |
| ExecutionContext exe_ctx(target_sp, /*get_process=*/true); | |
| exe_ctx.SetFramePtr(&next_frame); | |
| FindInterveningFrames(*next_func, *prev_func, exe_ctx, target, return_pc, | |
| path, images, log); | |
| // Push synthetic tail call frames. | |
| for (const auto &calleeInfo : llvm::reverse(path)) { | |
| Function *callee = calleeInfo.callee.function; | |
| uint32_t frame_idx = m_frames.size(); | |
| uint32_t concrete_frame_idx = next_frame.GetConcreteFrameIndex(); | |
| addr_t cfa = LLDB_INVALID_ADDRESS; | |
| bool cfa_is_valid = false; | |
| addr_t pc = calleeInfo.address; | |
| // If the callee address refers to the call instruction, we do not want to | |
| // subtract 1 from this value. | |
| const bool artificial = true; | |
| const bool behaves_like_zeroth_frame = | |
| calleeInfo.address_type == CallEdge::AddrType::Call; | |
| SymbolContext sc; | |
| callee->CalculateSymbolContext(&sc); | |
| auto synth_frame = std::make_shared<StackFrame>( | |
| 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_id = GetIdentifier(); | |
| m_frames.push_back(synth_frame); | |
| LLDB_LOG(log, "Pushed frame {0} at {1:x}", callee->GetDisplayName(), pc); | |
| } | |
| // If any frames were created, adjust next_frame's index. | |
| if (!path.empty()) | |
| next_frame.SetFrameIndex(m_frames.size()); | |
| } | |
| uint32_t StackFrameList::SynthesizeInlineFrames(StackFrameSP frame_sp, | |
| addr_t cfa) { | |
| SymbolContext unwind_sc = | |
| frame_sp->GetSymbolContext(eSymbolContextBlock | eSymbolContextFunction); | |
| if (!unwind_sc.block) | |
| return 0; | |
| TargetSP target_sp = m_thread.CalculateTarget(); | |
| uint32_t concrete_frame_idx = frame_sp->GetConcreteFrameIndex(); | |
| Address curr_frame_address(frame_sp->GetFrameCodeAddressForSymbolication()); | |
| SymbolContext next_frame_sc; | |
| Address next_frame_address; | |
| uint32_t num_inlined_frames = 0; | |
| const bool behaves_like_zeroth_frame = frame_sp->m_behaves_like_zeroth_frame; | |
| while (unwind_sc.GetParentOfInlinedScope(curr_frame_address, next_frame_sc, | |
| next_frame_address)) { | |
| next_frame_sc.line_entry.ApplyFileMappings(target_sp); | |
| StackFrameSP inline_frame_sp = std::make_shared<StackFrame>( | |
| m_thread.shared_from_this(), m_frames.size(), concrete_frame_idx, | |
| frame_sp->GetRegisterContextSP(), cfa, next_frame_address, | |
| behaves_like_zeroth_frame, &next_frame_sc); | |
| inline_frame_sp->m_frame_list_id = GetIdentifier(); | |
| m_frames.push_back(inline_frame_sp); | |
| unwind_sc = next_frame_sc; | |
| curr_frame_address = next_frame_address; | |
| ++num_inlined_frames; | |
| } | |
| return num_inlined_frames; | |
| } | |
| bool StackFrameList::GetFramesUpTo(uint32_t end_idx, | |
| InterruptionControl allow_interrupt) { | |
| // GetFramesUpTo is always called with the intent to add frames, so get the | |
| // writer lock: | |
| llvm::sys::ScopedWriter guard(m_list_mutex); | |
| // Now that we have the lock, check to make sure someone didn't get there | |
| // ahead of us: | |
| if (m_frames.size() > end_idx || GetAllFramesFetched()) | |
| return false; | |
| // Do not fetch frames for an invalid thread. | |
| bool was_interrupted = false; | |
| if (!m_thread.IsValid()) | |
| return false; | |
| // lock the writer side of m_list_mutex as we're going to add frames here: | |
| if (!m_show_inlined_frames) { | |
| if (end_idx < m_concrete_frames_fetched) | |
| return false; | |
| // We're adding concrete frames now: | |
| // FIXME: This should also be interruptible: | |
| FetchOnlyConcreteFramesUpTo(end_idx); | |
| return false; | |
| } | |
| // We're adding concrete and inlined frames now: | |
| was_interrupted = FetchFramesUpTo(end_idx, allow_interrupt); | |
| #if defined(DEBUG_STACK_FRAMES) | |
| s.PutCString("\n\nNew frames:\n"); | |
| Dump(&s); | |
| s.EOL(); | |
| #endif | |
| return was_interrupted; | |
| } | |
| void StackFrameList::FetchOnlyConcreteFramesUpTo(uint32_t end_idx) { | |
| assert(m_thread.IsValid() && "Expected valid thread"); | |
| assert(m_frames.size() <= end_idx && "Expected there to be frames to fill"); | |
| Unwind &unwinder = m_thread.GetUnwinder(); | |
| if (end_idx < m_concrete_frames_fetched) | |
| return; | |
| uint32_t num_frames = unwinder.GetFramesUpTo(end_idx); | |
| if (num_frames <= end_idx + 1) { | |
| // Done unwinding. | |
| m_concrete_frames_fetched = UINT32_MAX; | |
| } | |
| // Don't create the frames eagerly. Defer this work to GetFrameAtIndex, | |
| // which can lazily query the unwinder to create frames. | |
| m_frames.resize(num_frames); | |
| } | |
| bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, | |
| InterruptionControl allow_interrupt) { | |
| Unwind &unwinder = m_thread.GetUnwinder(); | |
| bool was_interrupted = false; | |
| #if defined(DEBUG_STACK_FRAMES) | |
| StreamFile s(stdout, false); | |
| #endif | |
| // If we are hiding some frames from the outside world, we need to add | |
| // those onto the total count of frames to fetch. However, we don't need | |
| // to do that if end_idx is 0 since in that case we always get the first | |
| // concrete frame and all the inlined frames below it... And of course, if | |
| // end_idx is UINT32_MAX that means get all, so just do that... | |
| uint32_t inlined_depth = 0; | |
| if (end_idx > 0 && end_idx != UINT32_MAX) { | |
| inlined_depth = GetCurrentInlinedDepth(); | |
| if (inlined_depth != UINT32_MAX) { | |
| if (end_idx > 0) | |
| end_idx += inlined_depth; | |
| } | |
| } | |
| StackFrameSP unwind_frame_sp; | |
| Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); | |
| do { | |
| uint32_t idx = m_concrete_frames_fetched++; | |
| lldb::addr_t pc = LLDB_INVALID_ADDRESS; | |
| lldb::addr_t cfa = LLDB_INVALID_ADDRESS; | |
| bool behaves_like_zeroth_frame = (idx == 0); | |
| if (idx == 0) { | |
| // We might have already created frame zero, only create it if we need | |
| // to. | |
| if (m_frames.empty()) { | |
| RegisterContextSP reg_ctx_sp(m_thread.GetRegisterContext()); | |
| if (reg_ctx_sp) { | |
| const bool success = unwinder.GetFrameInfoAtIndex( | |
| idx, cfa, pc, behaves_like_zeroth_frame); | |
| // There shouldn't be any way not to get the frame info for frame | |
| // 0. But if the unwinder can't make one, lets make one by hand | |
| // with the SP as the CFA and see if that gets any further. | |
| if (!success) { | |
| cfa = reg_ctx_sp->GetSP(); | |
| pc = reg_ctx_sp->GetPC(); | |
| } | |
| 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_id = GetIdentifier(); | |
| m_frames.push_back(unwind_frame_sp); | |
| } | |
| } else { | |
| unwind_frame_sp = m_frames.front(); | |
| cfa = unwind_frame_sp->m_id.GetCallFrameAddressWithoutMetadata(); | |
| } | |
| } else { | |
| // Check for interruption when building the frames. | |
| // Do the check in idx > 0 so that we'll always create a 0th frame. | |
| if (allow_interrupt && | |
| INTERRUPT_REQUESTED(dbg, "Interrupted having fetched {0} frames", | |
| m_frames.size())) { | |
| was_interrupted = true; | |
| break; | |
| } | |
| const bool success = | |
| unwinder.GetFrameInfoAtIndex(idx, cfa, pc, behaves_like_zeroth_frame); | |
| if (!success) { | |
| // We've gotten to the end of the stack. | |
| SetAllFramesFetched(); | |
| break; | |
| } | |
| const bool cfa_is_valid = true; | |
| unwind_frame_sp = std::make_shared<StackFrame>( | |
| m_thread.shared_from_this(), m_frames.size(), idx, cfa, cfa_is_valid, | |
| pc, StackFrame::Kind::Regular, false, behaves_like_zeroth_frame, | |
| nullptr); | |
| // Create synthetic tail call frames between the previous frame and the | |
| // newly-found frame. The new frame's index may change after this call, | |
| // although its concrete index will stay the same. | |
| SynthesizeTailCallFrames(*unwind_frame_sp.get()); | |
| unwind_frame_sp->m_frame_list_id = GetIdentifier(); | |
| m_frames.push_back(unwind_frame_sp); | |
| } | |
| assert(unwind_frame_sp); | |
| SynthesizeInlineFrames(unwind_frame_sp, cfa); | |
| } while (m_frames.size() - 1 < end_idx); | |
| // Don't try to merge till you've calculated all the frames in this stack. | |
| if (GetAllFramesFetched() && m_prev_frames_sp) { | |
| StackFrameList *prev_frames = m_prev_frames_sp.get(); | |
| StackFrameList *curr_frames = this; | |
| #if defined(DEBUG_STACK_FRAMES) | |
| s.PutCString("\nprev_frames:\n"); | |
| prev_frames->Dump(&s); | |
| s.PutCString("\ncurr_frames:\n"); | |
| curr_frames->Dump(&s); | |
| s.EOL(); | |
| #endif | |
| size_t curr_frame_num, prev_frame_num; | |
| for (curr_frame_num = curr_frames->m_frames.size(), | |
| prev_frame_num = prev_frames->m_frames.size(); | |
| curr_frame_num > 0 && prev_frame_num > 0; | |
| --curr_frame_num, --prev_frame_num) { | |
| const size_t curr_frame_idx = curr_frame_num - 1; | |
| const size_t prev_frame_idx = prev_frame_num - 1; | |
| StackFrameSP curr_frame_sp(curr_frames->m_frames[curr_frame_idx]); | |
| StackFrameSP prev_frame_sp(prev_frames->m_frames[prev_frame_idx]); | |
| #if defined(DEBUG_STACK_FRAMES) | |
| s.Printf("\n\nCurr frame #%u ", curr_frame_idx); | |
| if (curr_frame_sp) | |
| curr_frame_sp->Dump(&s, true, false); | |
| else | |
| s.PutCString("NULL"); | |
| s.Printf("\nPrev frame #%u ", prev_frame_idx); | |
| if (prev_frame_sp) | |
| prev_frame_sp->Dump(&s, true, false); | |
| else | |
| s.PutCString("NULL"); | |
| #endif | |
| StackFrame *curr_frame = curr_frame_sp.get(); | |
| StackFrame *prev_frame = prev_frame_sp.get(); | |
| if (curr_frame == nullptr || prev_frame == nullptr) | |
| break; | |
| // Check the stack ID to make sure they are equal. | |
| if (curr_frame->GetStackID() != prev_frame->GetStackID()) | |
| break; | |
| // Never adopt a frame borrowed from another StackFrameList, which only a | |
| // provider's SyntheticStackFrameList hands out: it keeps reporting the | |
| // index of the frame it borrows, and the update below cannot change | |
| // that. Skipping it is safe because the merge only carries cached state | |
| // onto a frame this list has already unwound correctly. | |
| if (llvm::isa<BorrowedStackFrame>(prev_frame)) | |
| continue; | |
| 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_id = GetIdentifier(); | |
| m_frames[curr_frame_idx] = prev_frame_sp; | |
| #if defined(DEBUG_STACK_FRAMES) | |
| s.PutCString("\n Copying previous frame to current frame"); | |
| #endif | |
| } | |
| // We are done with the old stack frame list, we can release it now. | |
| m_prev_frames_sp.reset(); | |
| } | |
| // Don't report interrupted if we happen to have gotten all the frames: | |
| if (!GetAllFramesFetched()) | |
| return was_interrupted; | |
| return false; | |
| } | |
| uint32_t StackFrameList::GetNumFrames(bool can_create) { | |
| if (!WereAllFramesFetched() && can_create) { | |
| // Don't allow interrupt or we might not return the correct count | |
| GetFramesUpTo(UINT32_MAX, DoNotAllowInterruption); | |
| } | |
| uint32_t frame_idx; | |
| { | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| frame_idx = GetVisibleStackFrameIndex(m_frames.size()); | |
| } | |
| return frame_idx; | |
| } | |
| void StackFrameList::Dump(Stream *s) { | |
| if (s == nullptr) | |
| return; | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| const_iterator pos, begin = m_frames.begin(), end = m_frames.end(); | |
| for (pos = begin; pos != end; ++pos) { | |
| StackFrame *frame = (*pos).get(); | |
| s->Printf("%p: ", static_cast<void *>(frame)); | |
| if (frame) { | |
| frame->GetStackID().Dump(s); | |
| frame->DumpUsingSettingsFormat(s); | |
| } else | |
| s->Printf("frame #%u", (uint32_t)std::distance(begin, pos)); | |
| s->EOL(); | |
| } | |
| s->EOL(); | |
| } | |
| StackFrameSP StackFrameList::GetFrameAtIndex(uint32_t idx) { | |
| StackFrameSP frame_sp; | |
| uint32_t original_idx = idx; | |
| // We're going to consult the m_frames.size, but if there are already | |
| // enough frames for our request we don't want to block other readers, so | |
| // first acquire the shared lock: | |
| { // Scope for shared lock: | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| uint32_t inlined_depth = GetCurrentInlinedDepth(); | |
| if (inlined_depth != UINT32_MAX) | |
| idx += inlined_depth; | |
| if (idx < m_frames.size()) | |
| frame_sp = m_frames[idx]; | |
| if (frame_sp) | |
| return frame_sp; | |
| } // End of reader lock scope | |
| // GetFramesUpTo will fill m_frames with as many frames as you asked for, if | |
| // there are that many. If there weren't then you asked for too many frames. | |
| // GetFramesUpTo returns true if interrupted: | |
| if (GetFramesUpTo(idx, AllowInterruption)) { | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOG(log, "GetFrameAtIndex was interrupted"); | |
| return {}; | |
| } | |
| { // Now we're accessing m_frames as a reader, so acquire the reader lock. | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| if (idx < m_frames.size()) { | |
| frame_sp = m_frames[idx]; | |
| } else if (original_idx == 0) { | |
| // There should ALWAYS be a frame at index 0. If something went wrong | |
| // with the CurrentInlinedDepth such that there weren't as many frames as | |
| // we thought taking that into account, then reset the current inlined | |
| // depth and return the real zeroth frame. | |
| if (m_frames.empty()) { | |
| // Why do we have a thread with zero frames, that should not ever | |
| // happen... | |
| assert(!m_thread.IsValid() && "A valid thread has no frames."); | |
| } else { | |
| ResetCurrentInlinedDepth(); | |
| frame_sp = m_frames[original_idx]; | |
| } | |
| } | |
| } // End of reader lock scope | |
| return frame_sp; | |
| } | |
| StackFrameSP | |
| StackFrameList::GetFrameWithConcreteFrameIndex(uint32_t unwind_idx) { | |
| // First try assuming the unwind index is the same as the frame index. The | |
| // unwind index is always greater than or equal to the frame index, so it is | |
| // a good place to start. If we have inlined frames we might have 5 concrete | |
| // frames (frame unwind indexes go from 0-4), but we might have 15 frames | |
| // after we make all the inlined frames. Most of the time the unwind frame | |
| // index (or the concrete frame index) is the same as the frame index. | |
| uint32_t frame_idx = unwind_idx; | |
| StackFrameSP frame_sp(GetFrameAtIndex(frame_idx)); | |
| while (frame_sp) { | |
| if (frame_sp->GetFrameIndex() == unwind_idx) | |
| break; | |
| frame_sp = GetFrameAtIndex(++frame_idx); | |
| } | |
| return frame_sp; | |
| } | |
| static bool CompareStackID(const StackFrameSP &stack_sp, | |
| const StackID &stack_id) { | |
| return stack_sp->GetStackID().IsYoungerThan(stack_id); | |
| } | |
| StackFrameSP StackFrameList::GetFrameWithStackID(const StackID &stack_id) { | |
| StackFrameSP frame_sp; | |
| if (stack_id.IsValid()) { | |
| uint32_t frame_idx = 0; | |
| { | |
| // First see if the frame is already realized. This is the scope for | |
| // the shared mutex: | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| // Do a binary search in case the stack frame is already in our cache | |
| collection::const_iterator pos = | |
| llvm::lower_bound(m_frames, stack_id, CompareStackID); | |
| if (pos != m_frames.end() && (*pos)->GetStackID() == stack_id) | |
| return *pos; | |
| } | |
| // If we needed to add more frames, we would get to here. | |
| do { | |
| frame_sp = GetFrameAtIndex(frame_idx); | |
| if (frame_sp && frame_sp->GetStackID() == stack_id) | |
| break; | |
| frame_idx++; | |
| } while (frame_sp); | |
| } | |
| return frame_sp; | |
| } | |
| bool StackFrameList::SetFrameAtIndex(uint32_t idx, StackFrameSP &frame_sp) { | |
| llvm::sys::ScopedWriter guard(m_list_mutex); | |
| if (idx >= m_frames.size()) | |
| m_frames.resize(idx + 1); | |
| // Make sure allocation succeeded by checking bounds again | |
| if (idx < m_frames.size()) { | |
| m_frames[idx] = frame_sp; | |
| return true; | |
| } | |
| return false; // resize failed, out of memory? | |
| } | |
| void StackFrameList::SelectMostRelevantFrame() { | |
| // Don't call into the frame recognizers while evaluating an expression on | |
| // the private state thread, as they can cause code to run in the inferior | |
| // process, and that can cause deadlocks when fetching stop events for the | |
| // expression. | |
| Policy policy = PolicyStack::Get().Current(); | |
| if (!policy.capabilities.can_run_frame_recognizers) | |
| return; | |
| Log *log = GetLog(LLDBLog::Thread); | |
| // Only the top frame should be recognized. | |
| StackFrameSP frame_sp = GetFrameAtIndex(0); | |
| if (!frame_sp) { | |
| LLDB_LOG(log, "Failed to construct Frame #0"); | |
| return; | |
| } | |
| RecognizedStackFrameSP recognized_frame_sp = frame_sp->GetRecognizedFrame(); | |
| if (recognized_frame_sp) { | |
| if (StackFrameSP most_relevant_frame_sp = | |
| recognized_frame_sp->GetMostRelevantFrame()) { | |
| LLDB_LOG(log, "Found most relevant frame at index {0}", | |
| most_relevant_frame_sp->GetFrameIndex()); | |
| SetSelectedFrame(most_relevant_frame_sp.get()); | |
| return; | |
| } | |
| } | |
| LLDB_LOG(log, "Frame #0 not recognized"); | |
| // If this thread has a non-trivial StopInfo, then let it suggest | |
| // a most relevant frame: | |
| StopInfoSP stop_info_sp = m_thread.GetStopInfo(); | |
| uint32_t stack_idx = 0; | |
| bool found_relevant = false; | |
| if (stop_info_sp) { | |
| // Here we're only asking the stop info if it wants to adjust the real stack | |
| // index. We have to ask about the m_inlined_stack_depth in | |
| // Thread::ShouldStop since the plans need to reason with that info. | |
| bool inlined = false; | |
| std::optional<uint32_t> stack_opt = | |
| stop_info_sp->GetSuggestedStackFrameIndex(inlined); | |
| if (stack_opt) { | |
| stack_idx = *stack_opt; | |
| found_relevant = true; | |
| } | |
| } | |
| frame_sp = GetFrameAtIndex(stack_idx); | |
| if (!frame_sp) | |
| LLDB_LOG(log, "Stop info suggested relevant frame {0} but it didn't exist", | |
| stack_idx); | |
| else if (found_relevant) | |
| LLDB_LOG(log, "Setting selected frame from stop info to {0}", stack_idx); | |
| // Note, we don't have to worry about "inlined" frames here, because we've | |
| // already calculated the inlined frame in Thread::ShouldStop, and | |
| // SetSelectedFrame will take care of that adjustment for us. | |
| SetSelectedFrame(frame_sp.get()); | |
| if (!found_relevant) | |
| LLDB_LOG(log, "No relevant frame!"); | |
| } | |
| uint32_t | |
| StackFrameList::GetSelectedFrameIndex(SelectMostRelevant select_most_relevant) { | |
| std::lock_guard<std::recursive_mutex> guard(m_selected_frame_mutex); | |
| if (!m_selected_frame_idx && select_most_relevant) | |
| SelectMostRelevantFrame(); | |
| if (!m_selected_frame_idx) { | |
| // If we aren't selecting the most relevant frame, and the selected frame | |
| // isn't set, then don't force a selection here, just return 0. | |
| if (!select_most_relevant) | |
| return 0; | |
| // If the inlined stack frame is set, then use that: | |
| m_selected_frame_idx = 0; | |
| } | |
| return *m_selected_frame_idx; | |
| } | |
| uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) { | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| std::lock_guard<std::recursive_mutex> selected_frame_guard( | |
| m_selected_frame_mutex); | |
| const_iterator pos; | |
| const_iterator begin = m_frames.begin(); | |
| const_iterator end = m_frames.end(); | |
| m_selected_frame_idx = 0; | |
| for (pos = begin; pos != end; ++pos) { | |
| if (pos->get() == frame) { | |
| m_selected_frame_idx = std::distance(begin, pos); | |
| uint32_t inlined_depth = GetCurrentInlinedDepth(); | |
| if (inlined_depth != UINT32_MAX) | |
| m_selected_frame_idx = *m_selected_frame_idx - inlined_depth; | |
| break; | |
| } | |
| } | |
| SetDefaultFileAndLineToSelectedFrame(); | |
| return *m_selected_frame_idx; | |
| } | |
| bool StackFrameList::SetSelectedFrameByIndex(uint32_t idx) { | |
| StackFrameSP frame_sp(GetFrameAtIndex(idx)); | |
| if (frame_sp) { | |
| SetSelectedFrame(frame_sp.get()); | |
| return true; | |
| } else | |
| return false; | |
| } | |
| void StackFrameList::SetDefaultFileAndLineToSelectedFrame() { | |
| if (m_thread.GetID() == | |
| m_thread.GetProcess()->GetThreadList().GetSelectedThread()->GetID()) { | |
| StackFrameSP frame_sp( | |
| GetFrameAtIndex(GetSelectedFrameIndex(DoNoSelectMostRelevantFrame))); | |
| if (frame_sp) { | |
| SymbolContext sc = frame_sp->GetSymbolContext(eSymbolContextLineEntry); | |
| if (sc.line_entry.GetFile()) | |
| m_thread.CalculateTarget()->GetSourceManager().SetDefaultFileAndLine( | |
| sc.line_entry.file_sp, sc.line_entry.line); | |
| } | |
| } | |
| } | |
| // The thread has been run, reset the number stack frames to zero so we can | |
| // determine how many frames we have lazily. | |
| // Note, we don't actually re-use StackFrameLists, we always make a new | |
| // StackFrameList every time we stop, and then copy frame information frame | |
| // by frame from the old to the new StackFrameList. So the comment above, | |
| // does not describe how StackFrameLists are currently used. | |
| // Clear is currently only used to clear the list in the destructor. | |
| void StackFrameList::Clear() { | |
| llvm::sys::ScopedWriter guard(m_list_mutex); | |
| m_frames.clear(); | |
| m_concrete_frames_fetched = 0; | |
| std::lock_guard<std::recursive_mutex> selected_frame_guard( | |
| m_selected_frame_mutex); | |
| m_selected_frame_idx.reset(); | |
| } | |
| lldb::StackFrameSP | |
| StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) { | |
| llvm::sys::ScopedReader guard(m_list_mutex); | |
| const_iterator pos; | |
| const_iterator begin = m_frames.begin(); | |
| const_iterator end = m_frames.end(); | |
| lldb::StackFrameSP ret_sp; | |
| for (pos = begin; pos != end; ++pos) { | |
| if (pos->get() == stack_frame_ptr) { | |
| ret_sp = (*pos); | |
| break; | |
| } | |
| } | |
| return ret_sp; | |
| } | |
| bool StackFrameList::IsNextFrameHidden(lldb_private::StackFrame &frame) { | |
| uint32_t frame_idx = frame.GetFrameIndex(); | |
| StackFrameSP frame_sp = GetFrameAtIndex(frame_idx + 1); | |
| if (!frame_sp) | |
| return false; | |
| return frame_sp->IsHidden(); | |
| } | |
| bool StackFrameList::IsPreviousFrameHidden(lldb_private::StackFrame &frame) { | |
| uint32_t frame_idx = frame.GetFrameIndex(); | |
| if (frame_idx == 0) | |
| return false; | |
| StackFrameSP frame_sp = GetFrameAtIndex(frame_idx - 1); | |
| if (!frame_sp) | |
| return false; | |
| return frame_sp->IsHidden(); | |
| } | |
| std::string StackFrameList::GetFrameMarker(lldb::StackFrameSP frame_sp, | |
| lldb::StackFrameSP selected_frame_sp, | |
| bool show_hidden_marker) { | |
| bool show_unicode_marker = Terminal::SupportsUnicode() && show_hidden_marker; | |
| if (frame_sp == selected_frame_sp) | |
| return show_unicode_marker ? " * " : "* "; | |
| if (!show_unicode_marker) | |
| return " "; | |
| if (IsPreviousFrameHidden(*frame_sp)) | |
| return reinterpret_cast<const char *>(u8"﹉ "); | |
| if (IsNextFrameHidden(*frame_sp)) | |
| return reinterpret_cast<const char *>(u8"﹍ "); | |
| return " "; | |
| } | |
| size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, | |
| uint32_t num_frames, bool show_frame_info, | |
| uint32_t num_frames_with_source, | |
| bool show_unique, bool show_hidden, | |
| bool show_hidden_marker, | |
| bool show_selected_frame) { | |
| size_t num_frames_displayed = 0; | |
| if (num_frames == 0) | |
| return 0; | |
| StackFrameSP frame_sp; | |
| uint32_t frame_idx = 0; | |
| uint32_t last_frame; | |
| // Don't let the last frame wrap around... | |
| if (num_frames == UINT32_MAX) | |
| last_frame = UINT32_MAX; | |
| else | |
| last_frame = first_frame + num_frames; | |
| StackFrameSP selected_frame_sp = | |
| m_thread.GetSelectedFrame(DoNoSelectMostRelevantFrame); | |
| std::string marker; | |
| for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) { | |
| frame_sp = GetFrameAtIndex(frame_idx); | |
| if (!frame_sp) | |
| break; | |
| if (show_selected_frame) | |
| marker = GetFrameMarker(frame_sp, selected_frame_sp, show_hidden_marker); | |
| else | |
| marker = GetFrameMarker(frame_sp, /*selected_frame_sp=*/nullptr, | |
| show_hidden_marker); | |
| // Hide uninteresting frames unless it's the selected frame. | |
| if (!show_hidden && frame_sp != selected_frame_sp && frame_sp->IsHidden()) | |
| continue; | |
| // Check for interruption here. If we're fetching arguments, this loop | |
| // can go slowly: | |
| Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); | |
| if (INTERRUPT_REQUESTED( | |
| dbg, "Interrupted dumping stack for thread {0:x} with {1} shown.", | |
| m_thread.GetID(), num_frames_displayed)) | |
| break; | |
| if (!frame_sp->GetStatus(strm, show_frame_info, | |
| num_frames_with_source > (first_frame - frame_idx), | |
| show_unique, marker)) | |
| break; | |
| ++num_frames_displayed; | |
| } | |
| strm.IndentLess(); | |
| return num_frames_displayed; | |
| } | |
| void StackFrameList::ClearSelectedFrameIndex() { m_selected_frame_idx.reset(); } |