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 | |