| //===-- ThreadPlanShouldStopHere.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/ThreadPlanShouldStopHere.h" | |
| #include "lldb/Symbol/Symbol.h" | |
| #include "lldb/Target/Language.h" | |
| #include "lldb/Target/LanguageRuntime.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| // ThreadPlanShouldStopHere constructor | |
| ThreadPlanShouldStopHere::ThreadPlanShouldStopHere(ThreadPlan *owner) | |
| : m_callbacks(), m_baton(nullptr), m_owner(owner), | |
| m_flags(ThreadPlanShouldStopHere::eNone) { | |
| m_callbacks.should_stop_here_callback = | |
| ThreadPlanShouldStopHere::DefaultShouldStopHereCallback; | |
| m_callbacks.step_from_here_callback = | |
| ThreadPlanShouldStopHere::DefaultStepFromHereCallback; | |
| } | |
| ThreadPlanShouldStopHere::ThreadPlanShouldStopHere( | |
| ThreadPlan *owner, const ThreadPlanShouldStopHereCallbacks *callbacks, | |
| void *baton) | |
| : m_callbacks(), m_baton(), m_owner(owner), | |
| m_flags(ThreadPlanShouldStopHere::eNone) { | |
| SetShouldStopHereCallbacks(callbacks, baton); | |
| } | |
| ThreadPlanShouldStopHere::~ThreadPlanShouldStopHere() = default; | |
| bool ThreadPlanShouldStopHere::InvokeShouldStopHereCallback( | |
| FrameComparison operation, Status &status) { | |
| bool should_stop_here = true; | |
| if (m_callbacks.should_stop_here_callback) { | |
| should_stop_here = m_callbacks.should_stop_here_callback( | |
| m_owner, m_flags, operation, status, m_baton); | |
| Log *log = GetLog(LLDBLog::Step); | |
| if (log) { | |
| lldb::addr_t current_addr = | |
| m_owner->GetThread().GetRegisterContext()->GetPC(0); | |
| LLDB_LOGF(log, "ShouldStopHere callback returned %u from 0x%" PRIx64 ".", | |
| should_stop_here, current_addr); | |
| } | |
| } | |
| return should_stop_here; | |
| } | |
| bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback( | |
| ThreadPlan *current_plan, Flags &flags, FrameComparison operation, | |
| Status &status, void *baton) { | |
| bool should_stop_here = true; | |
| StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get(); | |
| if (!frame) | |
| return true; | |
| Log *log = GetLog(LLDBLog::Step); | |
| if ((operation == eFrameCompareOlder && flags.Test(eStepOutAvoidNoDebug)) || | |
| (operation == eFrameCompareYounger && flags.Test(eStepInAvoidNoDebug)) || | |
| (operation == eFrameCompareSameParent && | |
| flags.Test(eStepInAvoidNoDebug))) { | |
| if (!frame->HasDebugInformation()) { | |
| LLDB_LOGF(log, "Stepping out of frame with no debug info"); | |
| should_stop_here = false; | |
| } | |
| } | |
| // Check whether the frame we are in is a language runtime thunk, only for | |
| // step out: | |
| if (operation == eFrameCompareOlder) { | |
| if (const Symbol *symbol = | |
| frame->GetSymbolContext(eSymbolContextSymbol).symbol) { | |
| ProcessSP process_sp(current_plan->GetThread().GetProcess()); | |
| for (auto *runtime : process_sp->GetLanguageRuntimes()) { | |
| if (runtime->IsSymbolARuntimeThunk(*symbol) && | |
| flags.Test(ThreadPlanShouldStopHere::eStepOutPastThunks)) { | |
| LLDB_LOGF( | |
| log, "Stepping out past a language thunk %s for: %s", | |
| frame->GetFunctionName(), | |
| Language::GetNameForLanguageType(runtime->GetLanguageType())); | |
| should_stop_here = false; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Source line stepping plans should always avoid code with line number 0. | |
| // But trampoline plans should not; it's easier to reason about if they | |
| // leave that up to the source line plan that's driving them. | |
| // FIXME: At present the ShouldStop and the StepFromHere calculate this | |
| // independently. If this ever becomes expensive (this one isn't) we can | |
| // try to have this set a state that the StepFromHere can use. | |
| if (frame && flags.Test(ThreadPlanShouldStopHere::eStepPastLine0)) { | |
| SymbolContext sc; | |
| sc = frame->GetSymbolContext(eSymbolContextLineEntry); | |
| if (sc.line_entry.line == 0) | |
| should_stop_here = false; | |
| } | |
| return should_stop_here; | |
| } | |
| ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback( | |
| ThreadPlan *current_plan, Flags &flags, FrameComparison operation, | |
| Status &status, void *baton) { | |
| const bool stop_others = false; | |
| const size_t frame_index = 0; | |
| ThreadPlanSP return_plan_sp; | |
| // If we are stepping through code at line number 0, then we need to step | |
| // over this range. Otherwise we will step out. | |
| Log *log = GetLog(LLDBLog::Step); | |
| StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get(); | |
| if (!frame) | |
| return return_plan_sp; | |
| SymbolContext sc; | |
| sc = frame->GetSymbolContext(eSymbolContextLineEntry | eSymbolContextSymbol); | |
| if (sc.line_entry.line == 0) { | |
| AddressRange range = sc.line_entry.range; | |
| bool just_step_out = false; | |
| if (sc.symbol) { | |
| ProcessSP process_sp(current_plan->GetThread().GetProcess()); | |
| // If this is a runtime thunk, step through it, rather than stepping out | |
| // because it's marked line 0. | |
| bool is_thunk = false; | |
| for (auto *runtime : process_sp->GetLanguageRuntimes()) { | |
| if (runtime->IsSymbolARuntimeThunk(*sc.symbol) && | |
| flags.Test(ThreadPlanShouldStopHere::eStepOutPastThunks)) { | |
| LLDB_LOGF( | |
| log, "Stepping out past a language thunk %s for: %s", | |
| frame->GetFunctionName(), | |
| Language::GetNameForLanguageType(runtime->GetLanguageType())); | |
| is_thunk = true; | |
| break; | |
| } | |
| } | |
| // If the whole function is marked line 0 just step out, that's easier & | |
| // faster than continuing to step through it. | |
| // FIXME: This assumes that the function is a single line range. It could | |
| // be a series of contiguous line 0 ranges. Check for that too. | |
| if (!is_thunk && sc.symbol->ValueIsAddress()) { | |
| Address symbol_end = sc.symbol->GetAddress(); | |
| symbol_end.Slide(sc.symbol->GetByteSize() - 1); | |
| if (range.ContainsFileAddress(sc.symbol->GetAddress()) && | |
| range.ContainsFileAddress(symbol_end)) { | |
| LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just " | |
| "stepping out."); | |
| just_step_out = true; | |
| } | |
| } | |
| } | |
| if (!just_step_out) { | |
| LLDB_LOGF(log, "ThreadPlanShouldStopHere::DefaultStepFromHereCallback " | |
| "Queueing StepInRange plan to step through line 0 code."); | |
| return_plan_sp = current_plan->GetThread().QueueThreadPlanForStepInRange( | |
| false, range, sc, llvm::StringRef(), eOnlyDuringStepping, status, | |
| eLazyBoolCalculate, eLazyBoolNo); | |
| } | |
| } | |
| if (!return_plan_sp) | |
| return_plan_sp = | |
| current_plan->GetThread().QueueThreadPlanForStepOutNoShouldStop( | |
| false, nullptr, true, stop_others, eVoteNo, eVoteNoOpinion, | |
| frame_index, status, true); | |
| return return_plan_sp; | |
| } | |
| ThreadPlanSP ThreadPlanShouldStopHere::QueueStepOutFromHerePlan( | |
| lldb_private::Flags &flags, lldb::FrameComparison operation, | |
| Status &status) { | |
| ThreadPlanSP return_plan_sp; | |
| if (m_callbacks.step_from_here_callback) { | |
| return_plan_sp = m_callbacks.step_from_here_callback( | |
| m_owner, flags, operation, status, m_baton); | |
| } | |
| return return_plan_sp; | |
| } | |
| lldb::ThreadPlanSP ThreadPlanShouldStopHere::CheckShouldStopHereAndQueueStepOut( | |
| lldb::FrameComparison operation, Status &status) { | |
| if (!InvokeShouldStopHereCallback(operation, status)) | |
| return QueueStepOutFromHerePlan(m_flags, operation, status); | |
| else | |
| return ThreadPlanSP(); | |
| } |