| 1 | //===-- ThreadPlanShouldStopHere.cpp --------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Target/ThreadPlanShouldStopHere.h" |
| 10 | #include "lldb/Symbol/Symbol.h" |
| 11 | #include "lldb/Target/Language.h" |
| 12 | #include "lldb/Target/LanguageRuntime.h" |
| 13 | #include "lldb/Target/RegisterContext.h" |
| 14 | #include "lldb/Target/Thread.h" |
| 15 | #include "lldb/Utility/LLDBLog.h" |
| 16 | #include "lldb/Utility/Log.h" |
| 17 | |
| 18 | using namespace lldb; |
| 19 | using namespace lldb_private; |
| 20 | |
| 21 | // ThreadPlanShouldStopHere constructor |
| 22 | ThreadPlanShouldStopHere::ThreadPlanShouldStopHere(ThreadPlan *owner) |
| 23 | : m_callbacks(), m_baton(nullptr), m_owner(owner), |
| 24 | m_flags(ThreadPlanShouldStopHere::eNone) { |
| 25 | m_callbacks.should_stop_here_callback = |
| 26 | ThreadPlanShouldStopHere::DefaultShouldStopHereCallback; |
| 27 | m_callbacks.step_from_here_callback = |
| 28 | ThreadPlanShouldStopHere::DefaultStepFromHereCallback; |
| 29 | } |
| 30 | |
| 31 | ThreadPlanShouldStopHere::ThreadPlanShouldStopHere( |
| 32 | ThreadPlan *owner, const ThreadPlanShouldStopHereCallbacks *callbacks, |
| 33 | void *baton) |
| 34 | : m_callbacks(), m_baton(), m_owner(owner), |
| 35 | m_flags(ThreadPlanShouldStopHere::eNone) { |
| 36 | SetShouldStopHereCallbacks(callbacks, baton); |
| 37 | } |
| 38 | |
| 39 | ThreadPlanShouldStopHere::~ThreadPlanShouldStopHere() = default; |
| 40 | |
| 41 | bool ThreadPlanShouldStopHere::InvokeShouldStopHereCallback( |
| 42 | FrameComparison operation, Status &status) { |
| 43 | bool should_stop_here = true; |
| 44 | if (m_callbacks.should_stop_here_callback) { |
| 45 | should_stop_here = m_callbacks.should_stop_here_callback( |
| 46 | m_owner, m_flags, operation, status, m_baton); |
| 47 | Log *log = GetLog(mask: LLDBLog::Step); |
| 48 | if (log) { |
| 49 | lldb::addr_t current_addr = |
| 50 | m_owner->GetThread().GetRegisterContext()->GetPC(fail_value: 0); |
| 51 | |
| 52 | LLDB_LOGF(log, "ShouldStopHere callback returned %u from 0x%" PRIx64 "." , |
| 53 | should_stop_here, current_addr); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | return should_stop_here; |
| 58 | } |
| 59 | |
| 60 | bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback( |
| 61 | ThreadPlan *current_plan, Flags &flags, FrameComparison operation, |
| 62 | Status &status, void *baton) { |
| 63 | bool should_stop_here = true; |
| 64 | StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(idx: 0).get(); |
| 65 | if (!frame) |
| 66 | return true; |
| 67 | |
| 68 | Log *log = GetLog(mask: LLDBLog::Step); |
| 69 | |
| 70 | if ((operation == eFrameCompareOlder && flags.Test(bit: eStepOutAvoidNoDebug)) || |
| 71 | (operation == eFrameCompareYounger && flags.Test(bit: eStepInAvoidNoDebug)) || |
| 72 | (operation == eFrameCompareSameParent && |
| 73 | flags.Test(bit: eStepInAvoidNoDebug))) { |
| 74 | if (!frame->HasDebugInformation()) { |
| 75 | LLDB_LOGF(log, "Stepping out of frame with no debug info" ); |
| 76 | |
| 77 | should_stop_here = false; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | // Check whether the frame we are in is a language runtime thunk, only for |
| 82 | // step out: |
| 83 | if (operation == eFrameCompareOlder) { |
| 84 | if (Symbol *symbol = frame->GetSymbolContext(resolve_scope: eSymbolContextSymbol).symbol) { |
| 85 | ProcessSP process_sp(current_plan->GetThread().GetProcess()); |
| 86 | for (auto *runtime : process_sp->GetLanguageRuntimes()) { |
| 87 | if (runtime->IsSymbolARuntimeThunk(symbol: *symbol) && |
| 88 | flags.Test(bit: ThreadPlanShouldStopHere::eStepOutPastThunks)) { |
| 89 | LLDB_LOGF( |
| 90 | log, "Stepping out past a language thunk %s for: %s" , |
| 91 | frame->GetFunctionName(), |
| 92 | Language::GetNameForLanguageType(runtime->GetLanguageType())); |
| 93 | should_stop_here = false; |
| 94 | break; |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | // Always avoid code with line number 0. |
| 100 | // FIXME: At present the ShouldStop and the StepFromHere calculate this |
| 101 | // independently. If this ever |
| 102 | // becomes expensive (this one isn't) we can try to have this set a state |
| 103 | // that the StepFromHere can use. |
| 104 | if (frame) { |
| 105 | SymbolContext sc; |
| 106 | sc = frame->GetSymbolContext(resolve_scope: eSymbolContextLineEntry); |
| 107 | if (sc.line_entry.line == 0) |
| 108 | should_stop_here = false; |
| 109 | } |
| 110 | |
| 111 | return should_stop_here; |
| 112 | } |
| 113 | |
| 114 | ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback( |
| 115 | ThreadPlan *current_plan, Flags &flags, FrameComparison operation, |
| 116 | Status &status, void *baton) { |
| 117 | const bool stop_others = false; |
| 118 | const size_t frame_index = 0; |
| 119 | ThreadPlanSP return_plan_sp; |
| 120 | // If we are stepping through code at line number 0, then we need to step |
| 121 | // over this range. Otherwise we will step out. |
| 122 | Log *log = GetLog(mask: LLDBLog::Step); |
| 123 | |
| 124 | StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(idx: 0).get(); |
| 125 | if (!frame) |
| 126 | return return_plan_sp; |
| 127 | SymbolContext sc; |
| 128 | sc = frame->GetSymbolContext(resolve_scope: eSymbolContextLineEntry | eSymbolContextSymbol); |
| 129 | |
| 130 | if (sc.line_entry.line == 0) { |
| 131 | AddressRange range = sc.line_entry.range; |
| 132 | bool just_step_out = false; |
| 133 | if (sc.symbol) { |
| 134 | ProcessSP process_sp(current_plan->GetThread().GetProcess()); |
| 135 | |
| 136 | // If this is a runtime thunk, step through it, rather than stepping out |
| 137 | // because it's marked line 0. |
| 138 | bool is_thunk = false; |
| 139 | for (auto *runtime : process_sp->GetLanguageRuntimes()) { |
| 140 | if (runtime->IsSymbolARuntimeThunk(symbol: *sc.symbol) && |
| 141 | flags.Test(bit: ThreadPlanShouldStopHere::eStepOutPastThunks)) { |
| 142 | LLDB_LOGF( |
| 143 | log, "Stepping out past a language thunk %s for: %s" , |
| 144 | frame->GetFunctionName(), |
| 145 | Language::GetNameForLanguageType(runtime->GetLanguageType())); |
| 146 | is_thunk = true; |
| 147 | break; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // If the whole function is marked line 0 just step out, that's easier & |
| 152 | // faster than continuing to step through it. |
| 153 | // FIXME: This assumes that the function is a single line range. It could |
| 154 | // be a series of contiguous line 0 ranges. Check for that too. |
| 155 | if (!is_thunk && sc.symbol->ValueIsAddress()) { |
| 156 | Address symbol_end = sc.symbol->GetAddress(); |
| 157 | symbol_end.Slide(offset: sc.symbol->GetByteSize() - 1); |
| 158 | if (range.ContainsFileAddress(so_addr: sc.symbol->GetAddress()) && |
| 159 | range.ContainsFileAddress(so_addr: symbol_end)) { |
| 160 | LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just " |
| 161 | "stepping out." ); |
| 162 | just_step_out = true; |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | if (!just_step_out) { |
| 167 | LLDB_LOGF(log, "ThreadPlanShouldStopHere::DefaultStepFromHereCallback " |
| 168 | "Queueing StepInRange plan to step through line 0 code." ); |
| 169 | |
| 170 | return_plan_sp = current_plan->GetThread().QueueThreadPlanForStepInRange( |
| 171 | abort_other_plans: false, range, addr_context: sc, step_in_target: nullptr, stop_other_threads: eOnlyDuringStepping, status, |
| 172 | step_in_avoids_code_without_debug_info: eLazyBoolCalculate, step_out_avoids_code_without_debug_info: eLazyBoolNo); |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (!return_plan_sp) |
| 177 | return_plan_sp = |
| 178 | current_plan->GetThread().QueueThreadPlanForStepOutNoShouldStop( |
| 179 | abort_other_plans: false, addr_context: nullptr, first_insn: true, stop_other_threads: stop_others, report_stop_vote: eVoteNo, report_run_vote: eVoteNoOpinion, |
| 180 | frame_idx: frame_index, status, continue_to_next_branch: true); |
| 181 | return return_plan_sp; |
| 182 | } |
| 183 | |
| 184 | ThreadPlanSP ThreadPlanShouldStopHere::QueueStepOutFromHerePlan( |
| 185 | lldb_private::Flags &flags, lldb::FrameComparison operation, |
| 186 | Status &status) { |
| 187 | ThreadPlanSP return_plan_sp; |
| 188 | if (m_callbacks.step_from_here_callback) { |
| 189 | return_plan_sp = m_callbacks.step_from_here_callback( |
| 190 | m_owner, flags, operation, status, m_baton); |
| 191 | } |
| 192 | return return_plan_sp; |
| 193 | } |
| 194 | |
| 195 | lldb::ThreadPlanSP ThreadPlanShouldStopHere::CheckShouldStopHereAndQueueStepOut( |
| 196 | lldb::FrameComparison operation, Status &status) { |
| 197 | if (!InvokeShouldStopHereCallback(operation, status)) |
| 198 | return QueueStepOutFromHerePlan(flags&: m_flags, operation, status); |
| 199 | else |
| 200 | return ThreadPlanSP(); |
| 201 | } |
| 202 | |