diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index 953130d7ce97a..793028526138c 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2211,6 +2211,10 @@ bool PruneThreadPlansForTID(lldb::tid_t tid); /// Prune ThreadPlanStacks for all unreported threads. void PruneThreadPlans(); + void SynchronizeThreadPlans(); + + lldb::ThreadPlanSP FindDetachedPlanExplainingStop(Thread &thread, Event *event_ptr); + /// Find the thread plan stack associated with thread with \a tid. /// /// \param[in] tid @@ -2838,6 +2842,7 @@ void PruneThreadPlans(); /// threads in m_thread_list, as well as /// threads we knew existed, but haven't /// determined that they have died yet. + std::vector m_async_thread_plans; ThreadList m_extended_thread_list; ///< Owner for extended threads that may be ///generated, cleared on natural stops uint32_t m_extended_thread_stop_id; ///< The natural stop id when diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h index 546abdc301714..e603e0b81d7b4 100644 --- a/lldb/include/lldb/Target/ThreadPlan.h +++ b/lldb/include/lldb/Target/ThreadPlan.h @@ -478,6 +478,15 @@ class ThreadPlan : public std::enable_shared_from_this, return m_takes_iteration_count; } + bool IsTID(lldb::tid_t tid) { return tid == m_tid; } + bool HasTID() { return m_tid != LLDB_INVALID_THREAD_ID; } + void ClearTID() { m_tid = LLDB_INVALID_THREAD_ID; } + lldb::tid_t GetTID() { return m_tid; } + void SetTID(lldb::tid_t tid) { m_tid = tid; } + + friend lldb::ThreadPlanSP + Process::FindDetachedPlanExplainingStop(Thread &thread, Event *event_ptr); + protected: // Constructors and Destructors ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread, diff --git a/lldb/include/lldb/Target/ThreadPlanStack.h b/lldb/include/lldb/Target/ThreadPlanStack.h index ea0b86c644b29..f3e5823bbcd86 100644 --- a/lldb/include/lldb/Target/ThreadPlanStack.h +++ b/lldb/include/lldb/Target/ThreadPlanStack.h @@ -95,6 +95,10 @@ class ThreadPlanStack { /// generated. void ClearThreadCache(); + bool IsTID(lldb::tid_t tid); + lldb::tid_t GetTID(); + void SetTID(lldb::tid_t tid); + private: void PrintOneStack(Stream &s, llvm::StringRef stack_name, const PlanStack &stack, lldb::DescriptionLevel desc_level, @@ -152,8 +156,34 @@ class ThreadPlanStackMap { plan_list.second.ClearThreadCache(); } + // rename to Reactivate? + void Activate(ThreadPlanStack &&stack) { + if (m_plans_list.find(stack.GetTID()) == m_plans_list.end()) + m_plans_list.emplace(stack.GetTID(), std::move(stack)); + else + m_plans_list.at(stack.GetTID()) = std::move(stack); + } + + // rename to ...? + std::vector CleanUp() { + llvm::SmallVector invalidated_tids; + for (auto &pair : m_plans_list) + if (pair.second.GetTID() != pair.first) + invalidated_tids.push_back(pair.first); + + std::vector detached_stacks; + detached_stacks.reserve(invalidated_tids.size()); + for (auto tid : invalidated_tids) { + auto it = m_plans_list.find(tid); + auto stack = std::move(it->second); + m_plans_list.erase(it); + detached_stacks.emplace_back(std::move(stack)); + } + return detached_stacks; + } + void Clear() { - for (auto plan : m_plans_list) + for (auto &plan : m_plans_list) plan.second.ThreadDestroyed(nullptr); m_plans_list.clear(); } diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 73a9f99930955..96b4f49d9dabb 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1279,6 +1279,31 @@ void Process::UpdateThreadListIfNeeded() { } } +void Process::SynchronizeThreadPlans() { + for (auto &stack : m_thread_plans.CleanUp()) + m_async_thread_plans.emplace_back(std::move(stack)); +} + +ThreadPlanSP Process::FindDetachedPlanExplainingStop(Thread &thread, + Event *event_ptr) { + auto end = m_async_thread_plans.end(); + for (auto it = m_async_thread_plans.begin(); it != end; ++it) { + auto plan_sp = it->GetCurrentPlan(); + plan_sp->SetTID(thread.GetID()); + if (!plan_sp->DoPlanExplainsStop(event_ptr)) { + plan_sp->ClearTID(); + continue; + } + + auto stack = std::move(*it); + m_async_thread_plans.erase(it); + stack.SetTID(plan_sp->GetTID()); + m_thread_plans.Activate(std::move(stack)); + return plan_sp; + } + return {}; +} + ThreadPlanStack *Process::FindThreadPlans(lldb::tid_t tid) { return m_thread_plans.Find(tid); } @@ -3483,8 +3508,10 @@ bool Process::ShouldBroadcastEvent(Event *event_ptr) { // restarted... Asking the thread list is also not likely to go well, // since we are running again. So in that case just report the event. - if (!was_restarted) + if (!was_restarted) { should_resume = !m_thread_list.ShouldStop(event_ptr); + SynchronizeThreadPlans(); + } if (was_restarted || should_resume || m_resume_requested) { Vote report_stop_vote = m_thread_list.ShouldReportStop(event_ptr); diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index ed40356bef604..6dc17996e2131 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -725,27 +725,22 @@ StackFrameList::GetFrameWithConcreteFrameIndex(uint32_t unwind_idx) { return frame_sp; } -static bool CompareStackID(const StackFrameSP &stack_sp, - const StackID &stack_id) { - return stack_sp->GetStackID() < stack_id; -} - StackFrameSP StackFrameList::GetFrameWithStackID(const StackID &stack_id) { StackFrameSP frame_sp; if (stack_id.IsValid()) { std::lock_guard guard(m_mutex); uint32_t frame_idx = 0; - // Do a binary search in case the stack frame is already in our cache + // Do a search in case the stack frame is already in our cache. collection::const_iterator begin = m_frames.begin(); collection::const_iterator end = m_frames.end(); if (begin != end) { collection::const_iterator pos = - std::lower_bound(begin, end, stack_id, CompareStackID); - if (pos != end) { - if ((*pos)->GetStackID() == stack_id) - return *pos; - } + std::find_if(begin, end, [&](StackFrameSP frame_sp) { + return frame_sp->GetStackID() == stack_id; + }); + if (pos != end) + return *pos; } do { frame_sp = GetFrameAtIndex(frame_idx); diff --git a/lldb/source/Target/StackID.cpp b/lldb/source/Target/StackID.cpp index 410d5b7e820a5..5ddce5885c63d 100644 --- a/lldb/source/Target/StackID.cpp +++ b/lldb/source/Target/StackID.cpp @@ -61,6 +61,13 @@ bool lldb_private::operator<(const StackID &lhs, const StackID &rhs) { const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress(); const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress(); + // FIXME: rdar://76119439 + // This heuristic is a *temporary* fallback while proper fixes are + // determined. The heuristic assumes the CFA of async functions is a low + // (heap) address, and for normal functions it's a high (stack) address. + if (lhs_cfa - rhs_cfa >= 0x00007ff000000000ULL) + return true; + // FIXME: We are assuming that the stacks grow downward in memory. That's not // necessary, but true on // all the machines we care about at present. If this changes, we'll have to diff --git a/lldb/source/Target/SwiftLanguageRuntimeNames.cpp b/lldb/source/Target/SwiftLanguageRuntimeNames.cpp index 1eb051dfc4e90..f903a21c09ae4 100644 --- a/lldb/source/Target/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Target/SwiftLanguageRuntimeNames.cpp @@ -13,8 +13,7 @@ #include "SwiftLanguageRuntimeImpl.h" #include "lldb/Target/SwiftLanguageRuntime.h" -#include "swift/ABI/Task.h" -#include "swift/Demangling/Demangle.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" #include "lldb/Symbol/Block.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/VariableList.h" @@ -23,6 +22,11 @@ #include "lldb/Target/ThreadPlanStepInRange.h" #include "lldb/Target/ThreadPlanStepOverRange.h" #include "lldb/Utility/Log.h" +#include "swift/ABI/Task.h" +#include "swift/Demangling/Demangle.h" + +#include "Plugins/Process/Utility/RegisterContext_x86.h" +#include "Utility/ARM64_DWARF_Registers.h" using namespace lldb; using namespace lldb_private; @@ -75,6 +79,8 @@ static bool IsSwiftAsyncFunctionSymbol(swift::Demangle::NodePointer node) { using namespace swift::Demangle; if (!node || node->getKind() != Node::Kind::Global) return false; + if (hasChild(node, Node::Kind::AsyncSuspendResumePartialFunction)) + return false; return childAtPath(node, {Node::Kind::Function, Node::Kind::Type, Node::Kind::FunctionType, Node::Kind::AsyncAnnotation}) || @@ -192,34 +198,29 @@ static ThunkAction GetThunkAction(ThunkKind kind) { class ThreadPlanStepInAsync : public ThreadPlan { public: static bool NeedsStep(SymbolContext &sc) { - if (sc.line_entry.IsValid() && sc.line_entry.line == 0) { + if (sc.line_entry.IsValid() && sc.line_entry.line == 0) // Compiler generated function, need to step in. return true; - } // TEMPORARY HACK WORKAROUND - if (!sc.symbol || !sc.comp_unit) { + if (!sc.symbol || !sc.comp_unit) return false; - } auto fn_start = sc.symbol->GetFileAddress(); auto fn_end = sc.symbol->GetFileAddress() + sc.symbol->GetByteSize(); int line_entry_count = 0; - if (auto line_table = sc.comp_unit->GetLineTable()) { + if (auto *line_table = sc.comp_unit->GetLineTable()) { for (uint32_t i = 0; i < line_table->GetSize(); ++i) { LineEntry line_entry; if (line_table->GetLineEntryAtIndex(i, line_entry)) { - if (!line_entry.IsValid() || line_entry.line == 0) { + if (!line_entry.IsValid() || line_entry.line == 0) continue; - } auto line_start = line_entry.range.GetBaseAddress().GetFileAddress(); - if (line_start >= fn_start && line_start < fn_end) { - if (++line_entry_count > 1) { + if (line_start >= fn_start && line_start < fn_end) + if (++line_entry_count > 1) // This is an async function with a proper body of code, no step // into `swift_task_switch` required. return false; - } - } } } } @@ -230,12 +231,9 @@ class ThreadPlanStepInAsync : public ThreadPlan { ThreadPlanStepInAsync(Thread &thread, SymbolContext &sc) : ThreadPlan(eKindGeneric, "step-in-async", thread, eVoteNoOpinion, eVoteNoOpinion) { - // Using the absence of line table entries as a heuristic, step into - // `swift_task_switch`. Then, a breakpoint can be set on the async function. assert(sc.function); - if (!sc.function) { + if (!sc.function) return; - } m_step_in_plan_sp = std::make_shared( thread, sc.function->GetAddressRange(), sc, "swift_task_switch", @@ -243,9 +241,8 @@ class ThreadPlanStepInAsync : public ThreadPlan { } void DidPush() override { - if (m_step_in_plan_sp) { + if (m_step_in_plan_sp) PushPlan(m_step_in_plan_sp); - } } bool ValidatePlan(Stream *error) override { return (bool)m_step_in_plan_sp; } @@ -255,16 +252,33 @@ class ThreadPlanStepInAsync : public ThreadPlan { s->PutCString("ThreadPlanStepInAsync"); } - // Composite thread plans never directly explain a stop. - bool DoPlanExplainsStop(Event *event) override { return false; } + bool DoPlanExplainsStop(Event *event) override { + if (!HasTID()) + return false; + + if (!m_async_breakpoint_sp) + return false; + + return GetBreakpointAsyncContext() == m_initial_async_ctx; + } + + bool ShouldStop(Event *event) override { + if (!m_async_breakpoint_sp) + return false; + + if (GetBreakpointAsyncContext() != m_initial_async_ctx) + return false; - // Async stops happen via breakpoint. - bool ShouldStop(Event *event) override { return false; } + SetPlanComplete(); + return true; + } bool MischiefManaged() override { - if (!m_step_in_plan_sp->IsPlanComplete()) { + if (IsPlanComplete()) + return true; + + if (!m_step_in_plan_sp->IsPlanComplete()) return false; - } if (!m_step_in_plan_sp->PlanSucceeded()) { // If the step in fails, then this plan fails. @@ -273,69 +287,123 @@ class ThreadPlanStepInAsync : public ThreadPlan { } if (!m_async_breakpoint_sp) { - m_async_breakpoint_sp = CreateAsyncBreakpoint(GetThread()); + auto &thread = GetThread(); + m_async_breakpoint_sp = CreateAsyncBreakpoint(thread); + m_initial_async_ctx = GetAsyncContext(thread.GetStackFrameAtIndex(1)); + ClearTID(); } - SetPlanComplete(); - return true; + return false; } - // Override ShouldStop of previous step plan. - bool ShouldAutoContinue(Event *event) override { return true; } - bool WillStop() override { return false; } lldb::StateType GetPlanRunState() override { return eStateRunning; } bool StopOthers() override { return false; } + void WillPop() override { + if (m_async_breakpoint_sp) + m_async_breakpoint_sp->GetTarget().RemoveBreakpointByID( + m_async_breakpoint_sp->GetID()); + } + private: - static constexpr std::ptrdiff_t taskOffsetOfRunJob64 = 8 * 5; - static constexpr std::ptrdiff_t taskOffsetOfRunJob32 = 4 * 5; -#if __POINTER_WIDTH__ == 64 - static_assert(offsetof(swift::AsyncTask, RunJob) == taskOffsetOfRunJob64, - "lldb assumes an incorrect offset of swift::Task::RunJob"); -#elif __POINTER_WIDTH__ == 32 - static_assert(offsetof(swift::AsyncTask, RunJob) == taskOffsetOfRunJob32, - "lldb assumes an incorrect offset of swift::Task::RunJob"); -#endif - - static BreakpointSP CreateAsyncBreakpoint(Thread &thread) { - auto frame_sp = thread.GetStackFrameAtIndex(0); - auto reg_ctx = frame_sp->GetRegisterContext(); - - // swift_task_switch(AsyncTask *, ExecutorRef, ExecutorRef) - constexpr auto task_regnum = LLDB_REGNUM_GENERIC_ARG1; - auto task_reg = reg_ctx->ConvertRegisterKindToRegisterNumber( - RegisterKind::eRegisterKindGeneric, task_regnum); - auto task_ptr = reg_ctx->ReadRegisterAsUnsigned(task_reg, 0); - if (!task_ptr) { + bool IsAtAsyncBreakpoint() { + auto stop_info_sp = GetPrivateStopInfo(); + if (!stop_info_sp) + return false; + + if (stop_info_sp->GetStopReason() != eStopReasonBreakpoint) + return false; + + auto &site_list = m_process.GetBreakpointSiteList(); + auto site_sp = site_list.FindByID(stop_info_sp->GetValue()); + if (!site_sp) + return false; + + return site_sp->IsBreakpointAtThisSite(m_async_breakpoint_sp->GetID()); + } + + llvm::Optional GetBreakpointAsyncContext() { + if (m_breakpoint_async_ctx) + return m_breakpoint_async_ctx; + + if (!IsAtAsyncBreakpoint()) return {}; - } - auto process_sp = thread.GetProcess(); - const auto sizeof_pointer = process_sp->GetAddressByteSize(); - auto run_job_ptr = task_ptr; - if (sizeof_pointer == 8) { - run_job_ptr += taskOffsetOfRunJob64; - } else if (sizeof_pointer == 4) { - run_job_ptr += taskOffsetOfRunJob32; + auto frame_sp = GetThread().GetStackFrameAtIndex(0); + auto async_ctx = GetAsyncContext(frame_sp); + + if (!IsIndirectContext(frame_sp)) { + m_breakpoint_async_ctx = async_ctx; + return m_breakpoint_async_ctx; } - Status status; - auto fn_ptr = process_sp->ReadPointerFromMemory(run_job_ptr, status); - if (status.Fail()) { + // Dereference the indirect async context. + auto process_sp = GetThread().GetProcess(); + Status error; + m_breakpoint_async_ctx = + process_sp->ReadPointerFromMemory(async_ctx, error); + return m_breakpoint_async_ctx; + } + + bool IsIndirectContext(lldb::StackFrameSP frame_sp) { + auto sc = frame_sp->GetSymbolContext(eSymbolContextSymbol); + auto mangled_name = sc.symbol->GetMangled().GetMangledName().GetStringRef(); + return SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol( + mangled_name); + } + + BreakpointSP CreateAsyncBreakpoint(Thread &thread) { + // The signature for `swift_task_switch` is as follows: + // SWIFT_CC(swiftasync) + // void swift_task_switch( + // SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext, + // TaskContinuationFunction *resumeFunction, + // ExecutorRef newExecutor); + // + // The async context given as the first argument is not passed using the + // calling convention's first register, it's passed in the platform's async + // context register. This means the `resumeFunction` parameter uses the + // first ABI register (ex: x86-64: rdi, arm64: x0). + auto reg_ctx = thread.GetStackFrameAtIndex(0)->GetRegisterContext(); + constexpr auto resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1; + auto resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber( + RegisterKind::eRegisterKindGeneric, resume_fn_regnum); + auto resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0); + if (!resume_fn_ptr) return {}; - } - auto breakpoint_sp = - process_sp->GetTarget().CreateBreakpoint(fn_ptr, true, false); + auto &target = thread.GetProcess()->GetTarget(); + auto breakpoint_sp = target.CreateBreakpoint(resume_fn_ptr, true, false); breakpoint_sp->SetBreakpointKind("async-step"); return breakpoint_sp; } + static lldb::addr_t GetAsyncContext(lldb::StackFrameSP frame_sp) { + auto reg_ctx_sp = frame_sp->GetRegisterContext(); + + int async_ctx_regnum = 0; + auto arch = reg_ctx_sp->CalculateTarget()->GetArchitecture(); + if (arch.GetMachine() == llvm::Triple::x86_64) { + async_ctx_regnum = dwarf_r14_x86_64; + } else if (arch.GetMachine() == llvm::Triple::aarch64) { + async_ctx_regnum = arm64_dwarf::x22; + } else { + assert(false && "swift async supports only x86_64 and arm64"); + return 0; + } + + auto async_ctx_reg = reg_ctx_sp->ConvertRegisterKindToRegisterNumber( + RegisterKind::eRegisterKindDWARF, async_ctx_regnum); + return reg_ctx_sp->ReadRegisterAsUnsigned(async_ctx_reg, 0); + } + ThreadPlanSP m_step_in_plan_sp; BreakpointSP m_async_breakpoint_sp; + llvm::Optional m_initial_async_ctx; + llvm::Optional m_breakpoint_async_ctx; }; static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 74e6650528936..2fa4263da7fcb 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -740,8 +740,6 @@ void Thread::DidResume() { SetResumeSignal(LLDB_INVALID_SIGNAL_NUMBER); } void Thread::DidStop() { SetState(eStateStopped); } bool Thread::ShouldStop(Event *event_ptr) { - ThreadPlan *current_plan = GetCurrentPlan(); - bool should_stop = true; Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP)); @@ -792,9 +790,6 @@ bool Thread::ShouldStop(Event *event_ptr) { LLDB_LOGF(log, "Plan stack initial state:\n%s", s.GetData()); } - // The top most plan always gets to do the trace log... - current_plan->DoTraceLog(); - // First query the stop info's ShouldStopSynchronous. This handles // "synchronous" stop reasons, for example the breakpoint command on internal // breakpoints. If a synchronous stop reason says we should not stop, then @@ -807,6 +802,16 @@ bool Thread::ShouldStop(Event *event_ptr) { return false; } + // Call this after ShouldStopSynchronous. + ThreadPlan *current_plan; + if (auto plan = GetProcess()->FindDetachedPlanExplainingStop(*this, event_ptr)) + current_plan = plan.get(); + else + current_plan = GetCurrentPlan(); + + // The top most plan always gets to do the trace log… + current_plan->DoTraceLog(); + // If we've already been restarted, don't query the plans since the state // they would examine is not current. if (Process::ProcessEventData::GetRestartedFromEvent(event_ptr)) diff --git a/lldb/source/Target/ThreadPlanStack.cpp b/lldb/source/Target/ThreadPlanStack.cpp index ca6a30a4f205e..c15e55b47416f 100644 --- a/lldb/source/Target/ThreadPlanStack.cpp +++ b/lldb/source/Target/ThreadPlanStack.cpp @@ -368,6 +368,13 @@ void ThreadPlanStack::WillResume() { m_discarded_plans.clear(); } +lldb::tid_t ThreadPlanStack::GetTID() { return GetCurrentPlan()->GetTID(); } + +void ThreadPlanStack::SetTID(lldb::tid_t tid) { + for (auto plan_sp : m_plans) + plan_sp->SetTID(tid); +} + void ThreadPlanStackMap::Update(ThreadList ¤t_threads, bool delete_missing, bool check_for_new) {