| //===-- ThreadPlan.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/ThreadPlan.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Symbol/CompileUnit.h" | |
| #include "lldb/Symbol/Function.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/StackFrame.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/State.h" | |
| #include <atomic> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| // ThreadPlan constructor | |
| ThreadPlan::ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread, | |
| Vote report_stop_vote, Vote report_run_vote) | |
| : m_process(*thread.GetProcess().get()), m_tid(thread.GetID()), | |
| m_report_stop_vote(report_stop_vote), m_report_run_vote(report_run_vote), | |
| m_takes_iteration_count(false), m_could_not_resolve_hw_bp(false), | |
| m_thread(&thread), m_kind(kind), m_name(name), m_plan_complete_mutex(), | |
| m_cached_plan_explains_stop(eLazyBoolCalculate), m_plan_complete(false), | |
| m_plan_private(false), m_okay_to_discard(true), | |
| m_is_controlling_plan(false), m_plan_succeeded(true) { | |
| SetID(GetNextID()); | |
| } | |
| // Destructor | |
| ThreadPlan::~ThreadPlan() = default; | |
| Target &ThreadPlan::GetTarget() { return m_process.GetTarget(); } | |
| const Target &ThreadPlan::GetTarget() const { return m_process.GetTarget(); } | |
| Thread &ThreadPlan::GetThread() { | |
| if (m_thread) | |
| return *m_thread; | |
| ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(m_tid); | |
| m_thread = thread_sp.get(); | |
| return *m_thread; | |
| } | |
| bool ThreadPlan::PlanExplainsStop(Event *event_ptr) { | |
| if (m_cached_plan_explains_stop == eLazyBoolCalculate) { | |
| bool actual_value = DoPlanExplainsStop(event_ptr); | |
| CachePlanExplainsStop(actual_value); | |
| return actual_value; | |
| } else { | |
| return m_cached_plan_explains_stop == eLazyBoolYes; | |
| } | |
| } | |
| bool ThreadPlan::IsPlanComplete() { | |
| std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); | |
| return m_plan_complete; | |
| } | |
| void ThreadPlan::SetPlanComplete(bool success) { | |
| std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); | |
| m_plan_complete = true; | |
| m_plan_succeeded = success; | |
| } | |
| bool ThreadPlan::MischiefManaged() { | |
| std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); | |
| // Mark the plan is complete, but don't override the success flag. | |
| m_plan_complete = true; | |
| return true; | |
| } | |
| Vote ThreadPlan::ShouldReportStop(Event *event_ptr) { | |
| Log *log = GetLog(LLDBLog::Step); | |
| if (m_report_stop_vote == eVoteNoOpinion) { | |
| ThreadPlan *prev_plan = GetPreviousPlan(); | |
| if (prev_plan) { | |
| Vote prev_vote = prev_plan->ShouldReportStop(event_ptr); | |
| LLDB_LOG(log, "returning previous thread plan vote: {0}", prev_vote); | |
| return prev_vote; | |
| } | |
| } | |
| LLDB_LOG(log, "Returning vote: {0}", m_report_stop_vote); | |
| return m_report_stop_vote; | |
| } | |
| Vote ThreadPlan::ShouldReportRun(Event *event_ptr) { | |
| if (m_report_run_vote == eVoteNoOpinion) { | |
| ThreadPlan *prev_plan = GetPreviousPlan(); | |
| if (prev_plan) | |
| return prev_plan->ShouldReportRun(event_ptr); | |
| } | |
| return m_report_run_vote; | |
| } | |
| void ThreadPlan::ClearThreadCache() { m_thread = nullptr; } | |
| bool ThreadPlan::StopOthers() { | |
| ThreadPlan *prev_plan; | |
| prev_plan = GetPreviousPlan(); | |
| return (prev_plan == nullptr) ? false : prev_plan->StopOthers(); | |
| } | |
| void ThreadPlan::SetStopOthers(bool new_value) { | |
| // SetStopOthers doesn't work up the hierarchy. You have to set the explicit | |
| // ThreadPlan you want to affect. | |
| } | |
| bool ThreadPlan::WillResume(StateType resume_state, bool current_plan) { | |
| m_cached_plan_explains_stop = eLazyBoolCalculate; | |
| if (current_plan) { | |
| Log *log = GetLog(LLDBLog::Step); | |
| if (log) { | |
| RegisterContext *reg_ctx = GetThread().GetRegisterContext().get(); | |
| assert(reg_ctx); | |
| addr_t pc = reg_ctx->GetPC(); | |
| addr_t sp = reg_ctx->GetSP(); | |
| addr_t fp = reg_ctx->GetFP(); | |
| LLDB_LOGF( | |
| log, | |
| "%s Thread #%u (0x%p): tid = 0x%4.4" PRIx64 ", pc = 0x%8.8" PRIx64 | |
| ", sp = 0x%8.8" PRIx64 ", fp = 0x%8.8" PRIx64 ", " | |
| "plan = '%s', state = %s, stop others = %d", | |
| __FUNCTION__, GetThread().GetIndexID(), | |
| static_cast<void *>(&GetThread()), m_tid, static_cast<uint64_t>(pc), | |
| static_cast<uint64_t>(sp), static_cast<uint64_t>(fp), m_name.c_str(), | |
| StateAsCString(resume_state), StopOthers()); | |
| } | |
| } | |
| bool success = DoWillResume(resume_state, current_plan); | |
| ClearThreadCache(); // We don't cache the thread pointer over resumes. This | |
| // Thread might go away, and another Thread represent | |
| // the same underlying object on a later stop. | |
| return success; | |
| } | |
| lldb::user_id_t ThreadPlan::GetNextID() { | |
| static std::atomic<uint32_t> g_nextPlanID{0}; | |
| return ++g_nextPlanID; | |
| } | |
| void ThreadPlan::DidPush() {} | |
| void ThreadPlan::DidPop() {} | |
| bool ThreadPlan::OkayToDiscard() { | |
| return IsControllingPlan() ? m_okay_to_discard : true; | |
| } | |
| lldb::StateType ThreadPlan::RunState() { | |
| if (m_tracer_sp && m_tracer_sp->TracingEnabled()) | |
| return eStateStepping; | |
| else | |
| return GetPlanRunState(); | |
| } | |
| bool ThreadPlan::IsUsuallyUnexplainedStopReason(lldb::StopReason reason) { | |
| switch (reason) { | |
| case eStopReasonWatchpoint: | |
| case eStopReasonSignal: | |
| case eStopReasonException: | |
| case eStopReasonExec: | |
| case eStopReasonThreadExiting: | |
| case eStopReasonInstrumentation: | |
| case eStopReasonFork: | |
| case eStopReasonVFork: | |
| case eStopReasonVForkDone: | |
| case eStopReasonInterrupt: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| // ThreadPlanNull | |
| ThreadPlanNull::ThreadPlanNull(Thread &thread) | |
| : ThreadPlan(ThreadPlan::eKindNull, "Null Thread Plan", thread, | |
| eVoteNoOpinion, eVoteNoOpinion) {} | |
| ThreadPlanNull::~ThreadPlanNull() = default; | |
| void ThreadPlanNull::GetDescription(Stream *s, lldb::DescriptionLevel level) { | |
| s->PutCString("Null thread plan - thread has been destroyed."); | |
| } | |
| bool ThreadPlanNull::ValidatePlan(Stream *error) { | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return true; | |
| } | |
| bool ThreadPlanNull::ShouldStop(Event *event_ptr) { | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return true; | |
| } | |
| bool ThreadPlanNull::WillStop() { | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return true; | |
| } | |
| bool ThreadPlanNull::DoPlanExplainsStop(Event *event_ptr) { | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, GetThread().GetID(), GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return true; | |
| } | |
| // The null plan is never done. | |
| bool ThreadPlanNull::MischiefManaged() { | |
| // The null plan is never done. | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return false; | |
| } | |
| lldb::StateType ThreadPlanNull::GetPlanRunState() { | |
| // Not sure what to return here. This is a dead thread. | |
| #ifdef LLDB_CONFIGURATION_DEBUG | |
| fprintf(stderr, | |
| "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 | |
| ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #else | |
| Log *log = GetLog(LLDBLog::Thread); | |
| LLDB_LOGF(log, | |
| "error: %s called on thread that has been destroyed " | |
| "(tid = 0x%" PRIx64 ", ptid = 0x%" PRIx64 ")", | |
| LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); | |
| #endif | |
| return eStateRunning; | |
| } | |
| llvm::Expected<std::vector<addr_t>> lldb_private::GetStepUntilAddresses( | |
| StackFrame &frame, const FileSpec &file, llvm::ArrayRef<uint32_t> lines, | |
| llvm::ArrayRef<addr_t> requested_addresses) { | |
| const SymbolContext &frame_sc = | |
| frame.GetSymbolContext(eSymbolContextCompUnit | eSymbolContextFunction); | |
| TargetSP target_sp = frame.CalculateTarget(); | |
| if (!target_sp) | |
| return llvm::createStringError("null target from StepUntil frame"); | |
| if (!frame_sc.comp_unit || !frame_sc.function) | |
| return llvm::createStringError("frame has no function debug information"); | |
| std::vector<addr_t> until_addrs; | |
| auto add_if_in_scope = [&](const Address &addr) { | |
| addr_t load_addr = addr.GetLoadAddress(target_sp.get()); | |
| if (load_addr != LLDB_INVALID_ADDRESS && frame.IsAddressInFrameScope(addr)) | |
| until_addrs.push_back(load_addr); | |
| }; | |
| bool found_some_line_table_entry = false; | |
| for (uint32_t line : lines) { | |
| LineEntry line_entry; | |
| uint32_t idx = frame_sc.comp_unit->FindLineEntry( | |
| 0, line, &file, /*exact=*/false, &line_entry); | |
| if (idx == UINT32_MAX) | |
| continue; | |
| found_some_line_table_entry = true; | |
| const uint32_t actual_line = line_entry.line; | |
| while (idx != UINT32_MAX) { | |
| add_if_in_scope(line_entry.range.GetBaseAddress()); | |
| idx = frame_sc.comp_unit->FindLineEntry(idx + 1, actual_line, &file, | |
| /*exact=*/true, &line_entry); | |
| } | |
| } | |
| for (addr_t address : requested_addresses) { | |
| Address addr; | |
| if (target_sp->ResolveLoadAddress(address, addr)) | |
| add_if_in_scope(addr); | |
| } | |
| if (!until_addrs.empty()) | |
| return until_addrs; | |
| // Historically, LLDB has emphasized the error when "only lines were | |
| // requested, but none of those lines had a line table entry". | |
| if (requested_addresses.empty() && !found_some_line_table_entry) | |
| return llvm::createStringError("No line entries matching until target"); | |
| return llvm::createStringError( | |
| "Until target outside of the current function"); | |
| } |