| //===-- 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/RegisterContext.h" |
| #include "lldb/Target/Thread.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(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_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(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_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; |
| } |
| } |
| |
| // Always avoid code with line number 0. |
| // 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) { |
| 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(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_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; |
| |
| // If the whole function is marked line 0 just step out, that's easier & |
| // faster than continuing to step through it. |
| bool just_step_out = false; |
| if (sc.symbol && 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, nullptr, 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(); |
| } |