1 /**
2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "debuggable_thread.h"
17
18 #include "breakpoint_storage.h"
19 #include "error.h"
20 #include "include/method.h"
21 #include "types/numeric_id.h"
22
23 namespace ark::tooling::inspector {
DebuggableThread(ManagedThread * thread,DebugInterface * debugger,SuspensionCallbacks && callbacks,BreakpointStorage & bpStorage)24 DebuggableThread::DebuggableThread(ManagedThread *thread, DebugInterface *debugger, SuspensionCallbacks &&callbacks,
25 BreakpointStorage &bpStorage)
26 : PtThreadEvaluationEngine(debugger, thread), callbacks_(std::move(callbacks)), state_(*this, bpStorage)
27 {
28 }
29
Reset()30 void DebuggableThread::Reset()
31 {
32 os::memory::LockHolder lock(mutex_);
33 state_.Reset();
34 }
35
BreakOnStart()36 void DebuggableThread::BreakOnStart()
37 {
38 os::memory::LockHolder lock(mutex_);
39 state_.BreakOnStart();
40 }
41
Continue()42 void DebuggableThread::Continue()
43 {
44 os::memory::LockHolder lock(mutex_);
45 state_.Continue();
46 Resume();
47 }
48
ContinueTo(std::unordered_set<PtLocation,HashLocation> locations)49 void DebuggableThread::ContinueTo(std::unordered_set<PtLocation, HashLocation> locations)
50 {
51 os::memory::LockHolder lock(mutex_);
52 state_.ContinueTo(std::move(locations));
53 Resume();
54 }
55
StepInto(std::unordered_set<PtLocation,HashLocation> locations)56 void DebuggableThread::StepInto(std::unordered_set<PtLocation, HashLocation> locations)
57 {
58 os::memory::LockHolder lock(mutex_);
59 state_.StepInto(std::move(locations));
60 Resume();
61 }
62
StepOver(std::unordered_set<PtLocation,HashLocation> locations)63 void DebuggableThread::StepOver(std::unordered_set<PtLocation, HashLocation> locations)
64 {
65 os::memory::LockHolder lock(mutex_);
66 state_.StepOver(std::move(locations));
67 Resume();
68 }
69
StepOut()70 void DebuggableThread::StepOut()
71 {
72 os::memory::LockHolder lock(mutex_);
73 state_.StepOut();
74 Resume();
75 }
76
IsPaused()77 bool DebuggableThread::IsPaused()
78 {
79 os::memory::LockHolder lock(mutex_);
80 return suspended_;
81 }
82
Touch()83 void DebuggableThread::Touch()
84 {
85 os::memory::LockHolder lock(mutex_);
86 Resume();
87 }
88
Pause()89 void DebuggableThread::Pause()
90 {
91 os::memory::LockHolder lock(mutex_);
92 state_.Pause();
93 }
94
SetSkipAllPauses(bool skip)95 void DebuggableThread::SetSkipAllPauses(bool skip)
96 {
97 os::memory::LockHolder lock(mutex_);
98 state_.SetSkipAllPauses(skip);
99 }
100
SetMixedDebugEnabled(bool mixedDebugEnabled)101 void DebuggableThread::SetMixedDebugEnabled(bool mixedDebugEnabled)
102 {
103 os::memory::LockHolder lock(mutex_);
104 state_.SetMixedDebugEnabled(mixedDebugEnabled);
105 }
106
SetPauseOnExceptions(PauseOnExceptionsState state)107 void DebuggableThread::SetPauseOnExceptions(PauseOnExceptionsState state)
108 {
109 os::memory::LockHolder lock(mutex_);
110 state_.SetPauseOnExceptions(state);
111 }
112
RequestToObjectRepository(std::function<void (ObjectRepository &)> request)113 bool DebuggableThread::RequestToObjectRepository(std::function<void(ObjectRepository &)> request)
114 {
115 os::memory::LockHolder lock(mutex_);
116
117 ASSERT(!request_.has_value());
118
119 if (!suspended_) {
120 return false;
121 }
122
123 ASSERT(GetManagedThread()->IsSuspended());
124
125 request_ = std::move(request);
126 GetManagedThread()->Resume();
127
128 while (request_) {
129 requestDone_.Wait(&mutex_);
130 }
131
132 ASSERT(suspended_);
133 ASSERT(GetManagedThread()->IsSuspended());
134
135 return true;
136 }
137
EvaluateExpression(uint32_t frameNumber,const ExpressionWrapper & bytecode)138 Expected<std::pair<RemoteObject, std::optional<RemoteObject>>, std::string> DebuggableThread::EvaluateExpression(
139 uint32_t frameNumber, const ExpressionWrapper &bytecode)
140 {
141 std::optional<RemoteObject> optResult;
142 std::optional<RemoteObject> optException;
143 std::optional<Error> optError;
144 RequestToObjectRepository(
145 [this, frameNumber, &bytecode, &optResult, &optException, &optError](ObjectRepository &objectRepo) {
146 Method *method = nullptr;
147 auto res = EvaluateExpression(frameNumber, bytecode, &method);
148 if (!res) {
149 HandleError(res.Error());
150 optError = res.Error();
151 return;
152 }
153
154 auto [result, exc] = res.Value();
155 optResult.emplace(objectRepo.CreateObject(result));
156 if (exc != nullptr) {
157 optException.emplace(objectRepo.CreateObject(TypedValue::Reference(exc)));
158 }
159 });
160 if (optError) {
161 ASSERT(!optResult);
162 return Unexpected(optError->GetMessage());
163 }
164 return std::make_pair(*optResult, optException);
165 }
166
OnException(bool uncaught)167 void DebuggableThread::OnException(bool uncaught)
168 {
169 if (IsEvaluating()) {
170 return;
171 }
172 os::memory::LockHolder lock(mutex_);
173 state_.OnException(uncaught);
174 while (state_.IsPaused()) {
175 ObjectRepository objectRepository;
176 Suspend(objectRepository, {}, GetManagedThread()->GetException(), state_.GetPauseReason());
177 }
178 }
179
OnFramePop()180 void DebuggableThread::OnFramePop()
181 {
182 if (IsEvaluating()) {
183 return;
184 }
185 os::memory::LockHolder lock(mutex_);
186 state_.OnFramePop();
187 }
188
OnMethodEntry()189 bool DebuggableThread::OnMethodEntry()
190 {
191 if (IsEvaluating()) {
192 return false;
193 }
194 os::memory::LockHolder lock(mutex_);
195 return state_.OnMethodEntry();
196 }
197
OnSingleStep(const PtLocation & location,const char * sourceFile)198 void DebuggableThread::OnSingleStep(const PtLocation &location, const char *sourceFile)
199 {
200 if (IsEvaluating()) {
201 return;
202 }
203 os::memory::LockHolder lock(mutex_);
204 state_.OnSingleStep(location, sourceFile);
205 while (state_.IsPaused()) {
206 ObjectRepository objectRepository;
207 auto hitBreakpoints = state_.GetBreakpointsByLocation(location);
208 Suspend(objectRepository, hitBreakpoints, {}, state_.GetPauseReason());
209 }
210 }
211
OnConsoleCall(const PandaVector<TypedValue> & arguments)212 std::vector<RemoteObject> DebuggableThread::OnConsoleCall(const PandaVector<TypedValue> &arguments)
213 {
214 std::vector<RemoteObject> result;
215
216 ObjectRepository objectRepository;
217 std::transform(arguments.begin(), arguments.end(), std::back_inserter(result),
218 [&objectRepository](auto value) { return objectRepository.CreateObject(value); });
219
220 return result;
221 }
222
Suspend(ObjectRepository & objectRepository,const std::vector<BreakpointId> & hitBreakpoints,ObjectHeader * exception,PauseReason pauseReason)223 void DebuggableThread::Suspend(ObjectRepository &objectRepository, const std::vector<BreakpointId> &hitBreakpoints,
224 ObjectHeader *exception, PauseReason pauseReason)
225 {
226 ASSERT(ManagedThread::GetCurrent() == GetManagedThread());
227
228 ASSERT(!suspended_);
229 ASSERT(!request_.has_value());
230
231 callbacks_.preSuspend(objectRepository, hitBreakpoints, exception);
232
233 suspended_ = true;
234 GetManagedThread()->Suspend();
235
236 callbacks_.postSuspend(objectRepository, hitBreakpoints, exception, pauseReason);
237
238 while (suspended_) {
239 mutex_.Unlock();
240
241 callbacks_.preWaitSuspension();
242 GetManagedThread()->WaitSuspension();
243 callbacks_.postWaitSuspension();
244
245 mutex_.Lock();
246
247 // We have three levels of suspension:
248 // - state_.IsPaused() - tells if the thread is paused on breakpoint or step;
249 // - suspended_ - tells if the thread generally sleeps (but could execute object repository requests);
250 // - GetManagedThread()->IsSuspended() - tells if the thread is actually sleeping
251 //
252 // If we are here, then the latter is false (thread waked up). The following variants are possible:
253 // - not paused and not suspended - e.g. continue / stepping action was performed;
254 // - not paused and suspended - invalid;
255 // - paused and not suspended - touch was performed, resume and sleep back
256 // (used to notify about the state on `runIfWaitingForDebugger`);
257 // - paused and suspended - object repository request was made.
258
259 ASSERT(suspended_ == request_.has_value());
260
261 if (request_) {
262 (*request_)(objectRepository);
263
264 request_.reset();
265 GetManagedThread()->Suspend();
266
267 requestDone_.Signal();
268 }
269 }
270 }
271
Resume()272 void DebuggableThread::Resume()
273 {
274 ASSERT(!request_.has_value());
275
276 if (!suspended_) {
277 return;
278 }
279
280 ASSERT(GetManagedThread()->IsSuspended());
281
282 callbacks_.preResume();
283
284 suspended_ = false;
285 GetManagedThread()->Resume();
286
287 callbacks_.postResume();
288 }
289 } // namespace ark::tooling::inspector
290