1 /**
2 * Copyright (c) 2021-2024 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 "debugger.h"
17
18 #include "evaluation/expression_loader.h"
19 #include "include/mem/panda_smart_pointers.h"
20 #include "include/stack_walker.h"
21 #include "include/stack_walker-inl.h"
22 #include "include/thread-inl.h"
23 #include "include/thread_scopes.h"
24 #include "include/tooling/pt_location.h"
25 #include "include/tooling/pt_thread.h"
26 #include "interpreter/frame.h"
27 #include "libpandabase/macros.h"
28 #include "libpandabase/os/mem.h"
29 #include "libpandabase/utils/expected.h"
30 #include "libpandabase/utils/span.h"
31 #include "libpandafile/bytecode_instruction.h"
32 #include "pt_scoped_managed_code.h"
33 #include "pt_thread_info.h"
34 #include "runtime/handle_scope-inl.h"
35
36 namespace ark::tooling {
37 // NOTE(maksenov): remove PtProperty class
FieldToPtProperty(Field * field)38 static PtProperty FieldToPtProperty(Field *field)
39 {
40 return PtProperty(field);
41 }
42
PtPropertyToField(PtProperty property)43 static Field *PtPropertyToField(PtProperty property)
44 {
45 return reinterpret_cast<Field *>(property.GetData());
46 }
47
SetNotification(PtThread thread,bool enable,PtHookType hookType)48 std::optional<Error> Debugger::SetNotification(PtThread thread, bool enable, PtHookType hookType)
49 {
50 if (thread == PtThread::NONE) {
51 if (enable) {
52 hooks_.EnableGlobalHook(hookType);
53 } else {
54 hooks_.DisableGlobalHook(hookType);
55 }
56 } else {
57 ManagedThread *managedThread = thread.GetManagedThread();
58 if (enable) {
59 managedThread->GetPtThreadInfo()->GetHookTypeInfo().Enable(hookType);
60 } else {
61 managedThread->GetPtThreadInfo()->GetHookTypeInfo().Disable(hookType);
62 }
63 }
64
65 return {};
66 }
67
CheckLocationInClass(const panda_file::File & pf,panda_file::File::EntityId classId,const PtLocation & location,std::optional<Error> & error)68 static bool CheckLocationInClass(const panda_file::File &pf, panda_file::File::EntityId classId,
69 const PtLocation &location, std::optional<Error> &error)
70 {
71 panda_file::ClassDataAccessor cda(pf, classId);
72 bool found = false;
73 cda.EnumerateMethods([&pf, &location, &error, &found](panda_file::MethodDataAccessor mda) {
74 if (mda.GetMethodId() == location.GetMethodId()) {
75 found = true;
76 auto codeId = mda.GetCodeId();
77 uint32_t codeSize = 0;
78 if (codeId.has_value()) {
79 panda_file::CodeDataAccessor codeDa(pf, *codeId);
80 codeSize = codeDa.GetCodeSize();
81 }
82 if (location.GetBytecodeOffset() >= codeSize) {
83 error = Error(Error::Type::INVALID_BREAKPOINT,
84 std::string("Invalid breakpoint location: bytecode offset (") +
85 std::to_string(location.GetBytecodeOffset()) + ") >= method code size (" +
86 std::to_string(codeSize) + ")");
87 }
88 return false;
89 }
90 return true;
91 });
92 return found;
93 }
94
CheckLocation(const PtLocation & location)95 std::optional<Error> Debugger::CheckLocation(const PtLocation &location)
96 {
97 std::optional<Error> res;
98 runtime_->GetClassLinker()->EnumeratePandaFiles([&location, &res](const panda_file::File &pf) {
99 if (pf.GetFilename() != location.GetPandaFile()) {
100 return true;
101 }
102
103 auto classes = pf.GetClasses();
104 bool found = false;
105 for (size_t i = 0; i < classes.Size(); i++) {
106 panda_file::File::EntityId id(classes[i]);
107 if (pf.IsExternal(id) || id.GetOffset() > location.GetMethodId().GetOffset()) {
108 continue;
109 }
110
111 found = CheckLocationInClass(pf, id, location, res);
112 if (found) {
113 break;
114 }
115 }
116 if (!found) {
117 res =
118 Error(Error::Type::METHOD_NOT_FOUND,
119 std::string("Cannot find method with id ") + std::to_string(location.GetMethodId().GetOffset()) +
120 " in panda file '" + std::string(location.GetPandaFile()) + "'");
121 }
122 return false;
123 });
124 return res;
125 }
126
SetBreakpoint(const PtLocation & location)127 std::optional<Error> Debugger::SetBreakpoint(const PtLocation &location)
128 {
129 auto error = CheckLocation(location);
130 if (error.has_value()) {
131 return error;
132 }
133
134 os::memory::WriteLockHolder wholder(rwlock_);
135 if (!breakpoints_.emplace(location).second) {
136 return Error(Error::Type::BREAKPOINT_ALREADY_EXISTS,
137 std::string("Breakpoint already exists: bytecode offset ") +
138 std::to_string(location.GetBytecodeOffset()));
139 }
140
141 return {};
142 }
143
RemoveBreakpoint(const PtLocation & location)144 std::optional<Error> Debugger::RemoveBreakpoint(const PtLocation &location)
145 {
146 if (!EraseBreakpoint(location)) {
147 return Error(Error::Type::BREAKPOINT_NOT_FOUND, "Breakpoint not found");
148 }
149
150 return {};
151 }
152
GetPandaFrame(StackWalker * pstack,uint32_t frameDepth,bool * outIsNative=nullptr)153 static ark::Frame *GetPandaFrame(StackWalker *pstack, uint32_t frameDepth, bool *outIsNative = nullptr)
154 {
155 ASSERT(pstack != nullptr);
156 StackWalker &stack = *pstack;
157
158 while (stack.HasFrame() && frameDepth != 0) {
159 stack.NextFrame();
160 --frameDepth;
161 }
162
163 bool isNative = false;
164 ark::Frame *frame = nullptr;
165 if (stack.HasFrame()) {
166 if (stack.IsCFrame()) {
167 isNative = true;
168 } else {
169 frame = stack.GetIFrame();
170 }
171 }
172
173 if (outIsNative != nullptr) {
174 *outIsNative = isNative;
175 }
176
177 return frame;
178 }
179
GetPandaFrame(ManagedThread * thread,uint32_t frameDepth=0,bool * outIsNative=nullptr)180 static ark::Frame *GetPandaFrame(ManagedThread *thread, uint32_t frameDepth = 0, bool *outIsNative = nullptr)
181 {
182 auto stack = StackWalker::Create(thread);
183 return GetPandaFrame(&stack, frameDepth, outIsNative);
184 }
185
GetThisAddrVRegByPandaFrame(ark::Frame * frame)186 static interpreter::StaticVRegisterRef GetThisAddrVRegByPandaFrame(ark::Frame *frame)
187 {
188 ASSERT(!frame->IsDynamic());
189 ASSERT(frame->GetMethod()->GetNumArgs() > 0);
190 uint32_t thisRegNum = frame->GetSize() - frame->GetMethod()->GetNumArgs();
191 return StaticFrameHandler(frame).GetVReg(thisRegNum);
192 }
193
GetThisAddrVRegByPandaFrameDyn(ark::Frame * frame)194 static interpreter::DynamicVRegisterRef GetThisAddrVRegByPandaFrameDyn(ark::Frame *frame)
195 {
196 ASSERT(frame->IsDynamic());
197 ASSERT(frame->GetMethod()->GetNumArgs() > 0);
198 uint32_t thisRegNum = frame->GetSize() - frame->GetMethod()->GetNumArgs();
199 return DynamicFrameHandler(frame).GetVReg(thisRegNum);
200 }
201
202 template <typename Callback>
GetPandaFrameByPtThread(PtThread thread,uint32_t frameDepth,Callback nativeFrameHandler)203 Expected<ark::Frame *, Error> GetPandaFrameByPtThread(PtThread thread, uint32_t frameDepth, Callback nativeFrameHandler)
204 {
205 ManagedThread *managedThread = thread.GetManagedThread();
206 ASSERT(managedThread != nullptr);
207
208 if (MTManagedThread::ThreadIsMTManagedThread(managedThread)) {
209 // Check if thread is suspended
210 MTManagedThread *mtManagedThread = MTManagedThread::CastFromThread(managedThread);
211 if (MTManagedThread::GetCurrent() != mtManagedThread && !mtManagedThread->IsUserSuspended()) {
212 return Unexpected(Error(Error::Type::THREAD_NOT_SUSPENDED,
213 std::string("Thread " + std::to_string(thread.GetId()) + " is not suspended")));
214 }
215 }
216
217 auto stack = StackWalker::Create(managedThread);
218 ark::Frame *frame = GetPandaFrame(&stack, frameDepth, nullptr);
219 if (frame == nullptr) {
220 // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
221 if constexpr (!std::is_same_v<decltype(nativeFrameHandler), std::nullptr_t>) {
222 nativeFrameHandler(&stack);
223 }
224 return Unexpected(Error(Error::Type::FRAME_NOT_FOUND,
225 std::string("Frame not found or native, threadId=" + std::to_string(thread.GetId()) +
226 " frameDepth=" + std::to_string(frameDepth))));
227 }
228 return frame;
229 }
230
GetVRegByPandaFrame(ark::Frame * frame,int32_t regNumber) const231 Expected<interpreter::StaticVRegisterRef, Error> Debugger::GetVRegByPandaFrame(ark::Frame *frame,
232 int32_t regNumber) const
233 {
234 if (regNumber == -1) {
235 return frame->GetAccAsVReg();
236 }
237
238 if (regNumber >= 0 && uint32_t(regNumber) < frame->GetSize()) {
239 return StaticFrameHandler(frame).GetVReg(uint32_t(regNumber));
240 }
241
242 return Unexpected(
243 Error(Error::Type::INVALID_REGISTER, std::string("Invalid register number: ") + std::to_string(regNumber)));
244 }
245
GetVRegByPandaFrameDyn(ark::Frame * frame,int32_t regNumber) const246 Expected<interpreter::DynamicVRegisterRef, Error> Debugger::GetVRegByPandaFrameDyn(ark::Frame *frame,
247 int32_t regNumber) const
248 {
249 if (regNumber == -1) {
250 return frame->template GetAccAsVReg<true>();
251 }
252
253 if (regNumber >= 0 && uint32_t(regNumber) < frame->GetSize()) {
254 return DynamicFrameHandler(frame).GetVReg(uint32_t(regNumber));
255 }
256
257 return Unexpected(
258 Error(Error::Type::INVALID_REGISTER, std::string("Invalid register number: ") + std::to_string(regNumber)));
259 }
260
GetThisVariableByFrame(PtThread thread,uint32_t frameDepth,ObjectHeader ** thisPtr)261 std::optional<Error> Debugger::GetThisVariableByFrame(PtThread thread, uint32_t frameDepth, ObjectHeader **thisPtr)
262 {
263 ASSERT_MANAGED_CODE();
264 *thisPtr = nullptr;
265
266 std::optional<Error> nativeError;
267
268 auto nativeFrameHandler = [thread, &nativeError, thisPtr](StackWalker *stack) {
269 if (!stack->GetCFrame().IsNative()) {
270 return;
271 }
272 if (stack->GetCFrame().GetMethod()->IsStatic()) {
273 nativeError =
274 Error(Error::Type::INVALID_VALUE, std::string("Static native method, no this address slot, threadId=" +
275 std::to_string(thread.GetId())));
276 return;
277 }
278 stack->IterateObjects([thisPtr](auto &vreg) {
279 // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
280 if constexpr (std::is_same_v<decltype(vreg), interpreter::StaticVRegisterRef &>) {
281 ASSERT(vreg.HasObject());
282 *thisPtr = vreg.GetReference();
283 }
284 return false;
285 });
286 };
287 auto ret = GetPandaFrameByPtThread(thread, frameDepth, nativeFrameHandler);
288 if (nativeError) {
289 return nativeError;
290 }
291 if (*thisPtr != nullptr) {
292 // The value was set by native frame handler
293 return {};
294 }
295 if (!ret) {
296 return ret.Error();
297 }
298 Frame *frame = ret.Value();
299 if (frame->GetMethod()->IsStatic()) {
300 return Error(Error::Type::INVALID_VALUE,
301 std::string("Static method, no this address slot, threadId=" + std::to_string(thread.GetId())));
302 }
303
304 if (frame->IsDynamic()) {
305 auto reg = GetThisAddrVRegByPandaFrameDyn(frame);
306 *thisPtr = reg.GetReference();
307 } else {
308 auto reg = GetThisAddrVRegByPandaFrame(ret.Value());
309 *thisPtr = reg.GetReference();
310 }
311 return {};
312 }
313
GetVariable(PtThread thread,uint32_t frameDepth,int32_t regNumber,VRegValue * result) const314 std::optional<Error> Debugger::GetVariable(PtThread thread, uint32_t frameDepth, int32_t regNumber,
315 VRegValue *result) const
316 {
317 ASSERT_MANAGED_CODE();
318 auto ret = GetPandaFrameByPtThread(thread, frameDepth, nullptr);
319 if (!ret) {
320 return ret.Error();
321 }
322
323 Frame *frame = ret.Value();
324 if (frame->IsDynamic()) {
325 auto reg = GetVRegByPandaFrameDyn(frame, regNumber);
326 if (!reg) {
327 return reg.Error();
328 }
329
330 auto vreg = reg.Value();
331 *result = VRegValue(vreg.GetValue());
332 return {};
333 }
334
335 auto reg = GetVRegByPandaFrame(ret.Value(), regNumber);
336 if (!reg) {
337 return reg.Error();
338 }
339
340 auto vreg = reg.Value();
341 *result = VRegValue(vreg.GetValue());
342 return {};
343 }
344
SetVariable(PtThread thread,uint32_t frameDepth,int32_t regNumber,const VRegValue & value) const345 std::optional<Error> Debugger::SetVariable(PtThread thread, uint32_t frameDepth, int32_t regNumber,
346 const VRegValue &value) const
347 {
348 ASSERT_MANAGED_CODE();
349 auto ret = GetPandaFrameByPtThread(thread, frameDepth, nullptr);
350 if (!ret) {
351 return ret.Error();
352 }
353
354 if (ret.Value()->IsDynamic()) {
355 auto reg = GetVRegByPandaFrameDyn(ret.Value(), regNumber);
356 if (!reg) {
357 return reg.Error();
358 }
359
360 auto vreg = reg.Value();
361 vreg.SetValue(value.GetValue());
362 return {};
363 }
364
365 auto reg = GetVRegByPandaFrame(ret.Value(), regNumber);
366 if (!reg) {
367 return reg.Error();
368 }
369
370 auto vreg = reg.Value();
371 vreg.SetValue(value.GetValue());
372 return {};
373 }
374
GetCurrentFrame(PtThread thread) const375 Expected<std::unique_ptr<PtFrame>, Error> Debugger::GetCurrentFrame(PtThread thread) const
376 {
377 ManagedThread *managedThread = thread.GetManagedThread();
378 ASSERT(managedThread != nullptr);
379
380 auto stack = StackWalker::Create(managedThread);
381 Method *method = stack.GetMethod();
382
383 Frame *interpreterFrame = nullptr;
384 if (!stack.IsCFrame()) {
385 interpreterFrame = stack.GetIFrame();
386 }
387
388 return {std::make_unique<PtDebugFrame>(method, interpreterFrame)};
389 }
390
EnumerateFrames(PtThread thread,std::function<bool (const PtFrame &)> callback) const391 std::optional<Error> Debugger::EnumerateFrames(PtThread thread, std::function<bool(const PtFrame &)> callback) const
392 {
393 ManagedThread *managedThread = thread.GetManagedThread();
394 ASSERT(managedThread != nullptr);
395
396 auto stack = StackWalker::Create(managedThread);
397 while (stack.HasFrame()) {
398 Method *method = stack.GetMethod();
399 Frame *frame = stack.IsCFrame() ? nullptr : stack.GetIFrame();
400 PtDebugFrame debugFrame(method, frame);
401 if (!callback(debugFrame)) {
402 break;
403 }
404 stack.NextFrame();
405 }
406
407 return {};
408 }
409
SuspendThread(PtThread thread) const410 std::optional<Error> Debugger::SuspendThread(PtThread thread) const
411 {
412 ManagedThread *managedThread = thread.GetManagedThread();
413 ASSERT(managedThread != nullptr);
414
415 if (!MTManagedThread::ThreadIsMTManagedThread(managedThread)) {
416 return Error(Error::Type::THREAD_NOT_FOUND,
417 std::string("Thread ") + std::to_string(thread.GetId()) + " is not MT Thread");
418 }
419 MTManagedThread *mtManagedThread = MTManagedThread::CastFromThread(managedThread);
420 mtManagedThread->Suspend();
421
422 return {};
423 }
424
ResumeThread(PtThread thread) const425 std::optional<Error> Debugger::ResumeThread(PtThread thread) const
426 {
427 ManagedThread *managedThread = thread.GetManagedThread();
428 ASSERT(managedThread != nullptr);
429
430 if (!MTManagedThread::ThreadIsMTManagedThread(managedThread)) {
431 return Error(Error::Type::THREAD_NOT_FOUND,
432 std::string("Thread ") + std::to_string(thread.GetId()) + " is not MT Thread");
433 }
434 MTManagedThread *mtManagedThread = MTManagedThread::CastFromThread(managedThread);
435 mtManagedThread->Resume();
436
437 return {};
438 }
439
EvaluateExpression(PtThread thread,uint32_t frameNumber,const ExpressionWrapper & expr,Method ** method,VRegValue * result) const440 std::optional<Error> Debugger::EvaluateExpression(PtThread thread, uint32_t frameNumber, const ExpressionWrapper &expr,
441 Method **method, VRegValue *result) const
442 {
443 ASSERT_MANAGED_CODE();
444 ASSERT(method != nullptr);
445 ASSERT(result != nullptr);
446 ASSERT(!thread.GetManagedThread()->HasPendingException());
447 // NOTE(dslynko): support custom frame depth
448 ASSERT(frameNumber == 0);
449
450 auto ret = GetPandaFrameByPtThread(thread, frameNumber, nullptr);
451 if (!ret) {
452 return ret.Error();
453 }
454
455 auto *ctx = ret.Value()->GetMethod()->GetClass()->GetLoadContext();
456 auto optMethod = LoadExpressionBytecode(ctx, expr);
457 if (!optMethod) {
458 return optMethod.Error();
459 }
460
461 *method = optMethod.Value();
462 return EvaluateExpression(thread, frameNumber, *method, result);
463 }
464
EvaluateExpression(PtThread thread,uint32_t frameNumber,Method * method,VRegValue * result) const465 std::optional<Error> Debugger::EvaluateExpression(PtThread thread, [[maybe_unused]] uint32_t frameNumber,
466 Method *method, VRegValue *result) const
467 {
468 ASSERT_MANAGED_CODE();
469 ASSERT(method != nullptr);
470 ASSERT(result != nullptr);
471 ASSERT(!thread.GetManagedThread()->HasPendingException());
472 // NOTE(dslynko): support custom frame depth
473 ASSERT(frameNumber == 0);
474
475 auto value = method->Invoke(thread.GetManagedThread(), nullptr);
476 *result = VRegValue(value.GetAsLong());
477 return {};
478 }
479
RestartFrame(PtThread thread,uint32_t frameNumber) const480 std::optional<Error> Debugger::RestartFrame(PtThread thread, uint32_t frameNumber) const
481 {
482 ManagedThread *managedThread = thread.GetManagedThread();
483 ASSERT(managedThread != nullptr);
484
485 if (!managedThread->IsUserSuspended()) {
486 return Error(Error::Type::THREAD_NOT_SUSPENDED,
487 std::string("Thread ") + std::to_string(thread.GetId()) + " is not suspended");
488 }
489
490 auto stack = StackWalker::Create(managedThread);
491 ark::Frame *popFrame = nullptr;
492 ark::Frame *retryFrame = nullptr;
493 uint32_t currentFrameNumber = 0;
494
495 while (stack.HasFrame()) {
496 if (stack.IsCFrame()) {
497 return Error(Error::Type::OPAQUE_FRAME, std::string("Thread ") + std::to_string(thread.GetId()) +
498 ", frame at depth is executing a native method");
499 }
500 if (currentFrameNumber == frameNumber) {
501 popFrame = stack.GetIFrame();
502 } else if (currentFrameNumber == (frameNumber + 1)) {
503 retryFrame = stack.GetIFrame();
504 break;
505 }
506 ++currentFrameNumber;
507 stack.NextFrame();
508 }
509
510 if (popFrame == nullptr) {
511 return Error(Error::Type::FRAME_NOT_FOUND, std::string("Thread ") + std::to_string(thread.GetId()) +
512 " doesn't have managed frame with number " +
513 std::to_string(frameNumber));
514 }
515
516 if (retryFrame == nullptr) {
517 return Error(Error::Type::NO_MORE_FRAMES, std::string("Thread ") + std::to_string(thread.GetId()) +
518 " does not have more than one frame on the call stack");
519 }
520
521 // Set force pop frames from top to target
522 stack.Reset(managedThread);
523 while (stack.HasFrame()) {
524 ark::Frame *frame = stack.GetIFrame();
525 frame->SetForcePop();
526 if (frame == popFrame) {
527 break;
528 }
529 stack.NextFrame();
530 }
531 retryFrame->SetRetryInstruction();
532
533 return {};
534 }
535
NotifyFramePop(PtThread thread,uint32_t depth) const536 std::optional<Error> Debugger::NotifyFramePop(PtThread thread, uint32_t depth) const
537 {
538 ManagedThread *managedThread = thread.GetManagedThread();
539 ASSERT(managedThread != nullptr);
540
541 /* NOTE: (cmd) the second NotifyFramePop is error. use one debugger instance to resolve this.
542 if (!mt_managed_thread->IsUserSuspended()) {
543 return Error(Error::Type::THREAD_NOT_SUSPENDED,
544 std::string("Thread ") + std::to_string(thread.GetId()) + " is not suspended");
545 }
546 */
547
548 bool isNative = false;
549 ark::Frame *popFrame = GetPandaFrame(managedThread, depth, &isNative);
550 if (popFrame == nullptr) {
551 if (isNative) {
552 return Error(Error::Type::OPAQUE_FRAME, std::string("Thread ") + std::to_string(thread.GetId()) +
553 ", frame at depth is executing a native method");
554 }
555
556 return Error(Error::Type::NO_MORE_FRAMES,
557 std::string("Thread ") + std::to_string(thread.GetId()) +
558 ", are no stack frames at the specified depth: " + std::to_string(depth));
559 }
560
561 popFrame->SetNotifyPop();
562 return {};
563 }
564
BytecodePcChanged(ManagedThread * thread,Method * method,uint32_t bcOffset)565 void Debugger::BytecodePcChanged(ManagedThread *thread, Method *method, uint32_t bcOffset)
566 {
567 ASSERT(bcOffset < method->GetCodeSize() && "code size of current method less then bc_offset");
568 PtLocation location(method->GetPandaFile()->GetFilename().c_str(), method->GetFileId(), bcOffset);
569
570 // Step event is reported before breakpoint, according to the spec.
571 HandleStep(thread, method, location);
572 HandleBreakpoint(thread, method, location);
573
574 if (IsPropertyWatchActive()) {
575 if (!HandlePropertyAccess(thread, method, location)) {
576 HandlePropertyModify(thread, method, location);
577 }
578 }
579 }
580
ObjectAlloc(BaseClass * klass,ObjectHeader * object,ManagedThread * thread,size_t size)581 void Debugger::ObjectAlloc(BaseClass *klass, ObjectHeader *object, ManagedThread *thread, size_t size)
582 {
583 if (!vmStarted_) {
584 return;
585 }
586 if (thread == nullptr) {
587 thread = ManagedThread::GetCurrent();
588 if (thread == nullptr) {
589 return;
590 }
591 }
592
593 hooks_.ObjectAlloc(klass, object, PtThread(thread), size);
594 }
595
MethodEntry(ManagedThread * managedThread,Method * method)596 void Debugger::MethodEntry(ManagedThread *managedThread, Method *method)
597 {
598 hooks_.MethodEntry(PtThread(managedThread), method);
599 }
600
MethodExit(ManagedThread * managedThread,Method * method)601 void Debugger::MethodExit(ManagedThread *managedThread, Method *method)
602 {
603 bool isExceptionTriggered = managedThread->HasPendingException();
604 VRegValue retValue(managedThread->GetCurrentFrame()->GetAcc().GetValue());
605 hooks_.MethodExit(PtThread(managedThread), method, isExceptionTriggered, retValue);
606
607 HandleNotifyFramePop(managedThread, method, isExceptionTriggered);
608 }
609
ClassLoad(Class * klass)610 void Debugger::ClassLoad(Class *klass)
611 {
612 auto *thread = Thread::GetCurrent();
613 if (!vmStarted_ || thread->GetThreadType() == Thread::ThreadType::THREAD_TYPE_COMPILER) {
614 return;
615 }
616
617 hooks_.ClassLoad(PtThread(ManagedThread::CastFromThread(thread)), klass);
618 }
619
ClassPrepare(Class * klass)620 void Debugger::ClassPrepare(Class *klass)
621 {
622 auto *thread = Thread::GetCurrent();
623 if (!vmStarted_ || thread->GetThreadType() == Thread::ThreadType::THREAD_TYPE_COMPILER) {
624 return;
625 }
626
627 hooks_.ClassPrepare(PtThread(ManagedThread::CastFromThread(thread)), klass);
628 }
629
MonitorWait(ObjectHeader * object,int64_t timeout)630 void Debugger::MonitorWait(ObjectHeader *object, int64_t timeout)
631 {
632 hooks_.MonitorWait(PtThread(ManagedThread::GetCurrent()), object, timeout);
633 }
634
MonitorWaited(ObjectHeader * object,bool timedOut)635 void Debugger::MonitorWaited(ObjectHeader *object, bool timedOut)
636 {
637 hooks_.MonitorWaited(PtThread(ManagedThread::GetCurrent()), object, timedOut);
638 }
639
MonitorContendedEnter(ObjectHeader * object)640 void Debugger::MonitorContendedEnter(ObjectHeader *object)
641 {
642 hooks_.MonitorContendedEnter(PtThread(ManagedThread::GetCurrent()), object);
643 }
644
MonitorContendedEntered(ObjectHeader * object)645 void Debugger::MonitorContendedEntered(ObjectHeader *object)
646 {
647 hooks_.MonitorContendedEntered(PtThread(ManagedThread::GetCurrent()), object);
648 }
649
HandleBreakpoint(ManagedThread * managedThread,Method * method,const PtLocation & location)650 bool Debugger::HandleBreakpoint(ManagedThread *managedThread, Method *method, const PtLocation &location)
651 {
652 {
653 os::memory::ReadLockHolder rholder(rwlock_);
654 if (!IsBreakpoint(location)) {
655 return false;
656 }
657 }
658
659 hooks_.Breakpoint(PtThread(managedThread), method, location);
660 return true;
661 }
662
ExceptionThrow(ManagedThread * thread,Method * method,ObjectHeader * exceptionObject,uint32_t bcOffset)663 void Debugger::ExceptionThrow(ManagedThread *thread, Method *method, ObjectHeader *exceptionObject, uint32_t bcOffset)
664 {
665 ASSERT(thread->HasPendingException());
666 HandleScope<ObjectHeader *> scope(thread);
667 VMHandle<ObjectHeader> handle(thread, exceptionObject);
668
669 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(*method);
670 std::pair<Method *, uint32_t> res = ctx.GetCatchMethodAndOffset(method, thread);
671 auto *catchMethodFile = res.first->GetPandaFile();
672
673 PtLocation throwLocation {method->GetPandaFile()->GetFilename().c_str(), method->GetFileId(), bcOffset};
674 PtLocation catchLocation {catchMethodFile->GetFilename().c_str(), res.first->GetFileId(), res.second};
675
676 hooks_.Exception(PtThread(thread), method, throwLocation, handle.GetPtr(), res.first, catchLocation);
677 }
678
ExceptionCatch(ManagedThread * thread,Method * method,ObjectHeader * exceptionObject,uint32_t bcOffset)679 void Debugger::ExceptionCatch(ManagedThread *thread, Method *method, ObjectHeader *exceptionObject, uint32_t bcOffset)
680 {
681 ASSERT(!thread->HasPendingException());
682
683 auto *pf = method->GetPandaFile();
684 PtLocation catchLocation {pf->GetFilename().c_str(), method->GetFileId(), bcOffset};
685
686 hooks_.ExceptionCatch(PtThread(thread), method, catchLocation, exceptionObject);
687 }
688
HandleStep(ManagedThread * managedThread,Method * method,const PtLocation & location)689 bool Debugger::HandleStep(ManagedThread *managedThread, Method *method, const PtLocation &location)
690 {
691 hooks_.SingleStep(PtThread(managedThread), method, location);
692 return true;
693 }
694
HandleNotifyFramePop(ManagedThread * managedThread,Method * method,bool wasPoppedByException)695 void Debugger::HandleNotifyFramePop(ManagedThread *managedThread, Method *method, bool wasPoppedByException)
696 {
697 ark::Frame *frame = GetPandaFrame(managedThread);
698 if (frame != nullptr && frame->IsNotifyPop()) {
699 hooks_.FramePop(PtThread(managedThread), method, wasPoppedByException);
700 frame->ClearNotifyPop();
701 }
702 }
703
ResolveField(ManagedThread * thread,const Method * caller,const BytecodeInstruction & inst)704 static Field *ResolveField(ManagedThread *thread, const Method *caller, const BytecodeInstruction &inst)
705 {
706 auto propertyIndex = inst.GetId().AsIndex();
707 auto propertyId = caller->GetClass()->ResolveFieldIndex(propertyIndex);
708 auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
709 ASSERT(classLinker);
710 ASSERT(!thread->HasPendingException());
711 auto *field = classLinker->GetField(*caller, propertyId);
712 if (UNLIKELY(field == nullptr)) {
713 // Field might be nullptr if a class was not found
714 thread->ClearException();
715 }
716 return field;
717 }
718
HandlePropertyAccess(ManagedThread * thread,Method * method,const PtLocation & location)719 bool Debugger::HandlePropertyAccess(ManagedThread *thread, Method *method, const PtLocation &location)
720 {
721 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
722 BytecodeInstruction inst(method->GetInstructions() + location.GetBytecodeOffset());
723 auto opcode = inst.GetOpcode();
724 bool isStatic = false;
725
726 switch (opcode) {
727 case BytecodeInstruction::Opcode::LDOBJ_V8_ID16:
728 case BytecodeInstruction::Opcode::LDOBJ_64_V8_ID16:
729 case BytecodeInstruction::Opcode::LDOBJ_OBJ_V8_ID16:
730 break;
731 case BytecodeInstruction::Opcode::LDSTATIC_ID16:
732 case BytecodeInstruction::Opcode::LDSTATIC_64_ID16:
733 case BytecodeInstruction::Opcode::LDSTATIC_OBJ_ID16:
734 isStatic = true;
735 break;
736 default:
737 return false;
738 }
739
740 Field *field = ResolveField(thread, method, inst);
741 if (field == nullptr) {
742 return false;
743 }
744 Class *klass = field->GetClass();
745 ASSERT(klass);
746
747 {
748 os::memory::ReadLockHolder rholder(rwlock_);
749 if (FindPropertyWatch(klass->GetFileId(), field->GetFileId(), PropertyWatch::Type::ACCESS) == nullptr) {
750 return false;
751 }
752 }
753
754 PtProperty ptProperty = FieldToPtProperty(field);
755
756 if (isStatic) {
757 hooks_.PropertyAccess(PtThread(thread), method, location, nullptr, ptProperty);
758 } else {
759 interpreter::VRegister ® = thread->GetCurrentFrame()->GetVReg(inst.GetVReg());
760 hooks_.PropertyAccess(PtThread(thread), method, location, reg.GetReference(), ptProperty);
761 }
762
763 return true;
764 }
765
HandlePropertyModify(ManagedThread * thread,Method * method,const PtLocation & location)766 bool Debugger::HandlePropertyModify(ManagedThread *thread, Method *method, const PtLocation &location)
767 {
768 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
769 BytecodeInstruction inst(method->GetInstructions() + location.GetBytecodeOffset());
770 auto opcode = inst.GetOpcode();
771 bool isStatic = false;
772
773 switch (opcode) {
774 case BytecodeInstruction::Opcode::STOBJ_V8_ID16:
775 case BytecodeInstruction::Opcode::STOBJ_64_V8_ID16:
776 case BytecodeInstruction::Opcode::STOBJ_OBJ_V8_ID16:
777 break;
778 case BytecodeInstruction::Opcode::STSTATIC_ID16:
779 case BytecodeInstruction::Opcode::STSTATIC_64_ID16:
780 case BytecodeInstruction::Opcode::STSTATIC_OBJ_ID16:
781 isStatic = true;
782 break;
783 default:
784 return false;
785 }
786
787 Field *field = ResolveField(thread, method, inst);
788 if (field == nullptr) {
789 return false;
790 }
791 Class *klass = field->GetClass();
792 ASSERT(klass);
793
794 {
795 os::memory::ReadLockHolder rholder(rwlock_);
796 if (FindPropertyWatch(klass->GetFileId(), field->GetFileId(), PropertyWatch::Type::MODIFY) == nullptr) {
797 return false;
798 }
799 }
800
801 PtProperty ptProperty = FieldToPtProperty(field);
802
803 VRegValue value(thread->GetCurrentFrame()->GetAcc().GetValue());
804 if (isStatic) {
805 hooks_.PropertyModification(PtThread(thread), method, location, nullptr, ptProperty, value);
806 } else {
807 interpreter::VRegister ® = thread->GetCurrentFrame()->GetVReg(inst.GetVReg());
808 hooks_.PropertyModification(PtThread(thread), method, location, reg.GetReference(), ptProperty, value);
809 }
810
811 return true;
812 }
813
SetPropertyAccessWatch(BaseClass * klass,PtProperty property)814 std::optional<Error> Debugger::SetPropertyAccessWatch(BaseClass *klass, PtProperty property)
815 {
816 os::memory::WriteLockHolder wholder(rwlock_);
817 ASSERT(!klass->IsDynamicClass());
818 panda_file::File::EntityId classId = static_cast<Class *>(klass)->GetFileId();
819 panda_file::File::EntityId propertyId = PtPropertyToField(property)->GetFileId();
820 if (FindPropertyWatch(classId, propertyId, PropertyWatch::Type::ACCESS) != nullptr) {
821 return Error(Error::Type::INVALID_PROPERTY_ACCESS_WATCH,
822 std::string("Invalid property access watch, already exist, ClassID: ") +
823 std::to_string(classId.GetOffset()) +
824 ", PropertyID: " + std::to_string(propertyId.GetOffset()));
825 }
826 propertyWatches_.emplace_back(classId, propertyId, PropertyWatch::Type::ACCESS);
827 return {};
828 }
829
ClearPropertyAccessWatch(BaseClass * klass,PtProperty property)830 std::optional<Error> Debugger::ClearPropertyAccessWatch(BaseClass *klass, PtProperty property)
831 {
832 ASSERT(!klass->IsDynamicClass());
833 panda_file::File::EntityId classId = static_cast<Class *>(klass)->GetFileId();
834 panda_file::File::EntityId propertyId = PtPropertyToField(property)->GetFileId();
835 if (!RemovePropertyWatch(classId, propertyId, PropertyWatch::Type::ACCESS)) {
836 return Error(Error::Type::PROPERTY_ACCESS_WATCH_NOT_FOUND,
837 std::string("Property access watch not found, ClassID: ") + std::to_string(classId.GetOffset()) +
838 ", PropertyID: " + std::to_string(propertyId.GetOffset()));
839 }
840 return {};
841 }
842
SetPropertyModificationWatch(BaseClass * klass,PtProperty property)843 std::optional<Error> Debugger::SetPropertyModificationWatch(BaseClass *klass, PtProperty property)
844 {
845 os::memory::WriteLockHolder wholder(rwlock_);
846 ASSERT(!klass->IsDynamicClass());
847 panda_file::File::EntityId classId = static_cast<Class *>(klass)->GetFileId();
848 panda_file::File::EntityId propertyId = PtPropertyToField(property)->GetFileId();
849 if (FindPropertyWatch(classId, propertyId, PropertyWatch::Type::MODIFY) != nullptr) {
850 return Error(Error::Type::INVALID_PROPERTY_MODIFY_WATCH,
851 std::string("Invalid property modification watch, already exist, ClassID: ") +
852 std::to_string(classId.GetOffset()) + ", PropertyID" + std::to_string(propertyId.GetOffset()));
853 }
854 propertyWatches_.emplace_back(classId, propertyId, PropertyWatch::Type::MODIFY);
855 return {};
856 }
857
ClearPropertyModificationWatch(BaseClass * klass,PtProperty property)858 std::optional<Error> Debugger::ClearPropertyModificationWatch(BaseClass *klass, PtProperty property)
859 {
860 ASSERT(!klass->IsDynamicClass());
861 panda_file::File::EntityId classId = static_cast<Class *>(klass)->GetFileId();
862 panda_file::File::EntityId propertyId = PtPropertyToField(property)->GetFileId();
863 if (!RemovePropertyWatch(classId, propertyId, PropertyWatch::Type::MODIFY)) {
864 return Error(Error::Type::PROPERTY_MODIFY_WATCH_NOT_FOUND,
865 std::string("Property modification watch not found, ClassID: ") +
866 std::to_string(classId.GetOffset()) + ", PropertyID" + std::to_string(propertyId.GetOffset()));
867 }
868 return {};
869 }
870
IsBreakpoint(const PtLocation & location) const871 bool Debugger::IsBreakpoint(const PtLocation &location) const REQUIRES_SHARED(rwlock_)
872 {
873 auto it = breakpoints_.find(location);
874 return it != breakpoints_.end();
875 }
876
EraseBreakpoint(const PtLocation & location)877 bool Debugger::EraseBreakpoint(const PtLocation &location)
878 {
879 os::memory::WriteLockHolder wholder(rwlock_);
880 auto it = breakpoints_.find(location);
881 if (it != breakpoints_.end()) {
882 breakpoints_.erase(it);
883 return true;
884 }
885 return false;
886 }
887
FindPropertyWatch(panda_file::File::EntityId classId,panda_file::File::EntityId fieldId,tooling::PropertyWatch::Type type) const888 const tooling::PropertyWatch *Debugger::FindPropertyWatch(panda_file::File::EntityId classId,
889 panda_file::File::EntityId fieldId,
890 tooling::PropertyWatch::Type type) const
891 REQUIRES_SHARED(rwlock_)
892 {
893 for (const auto &pw : propertyWatches_) {
894 if (pw.GetClassId() == classId && pw.GetFieldId() == fieldId && pw.GetType() == type) {
895 return &pw;
896 }
897 }
898
899 return nullptr;
900 }
901
RemovePropertyWatch(panda_file::File::EntityId classId,panda_file::File::EntityId fieldId,tooling::PropertyWatch::Type type)902 bool Debugger::RemovePropertyWatch(panda_file::File::EntityId classId, panda_file::File::EntityId fieldId,
903 tooling::PropertyWatch::Type type)
904 {
905 os::memory::WriteLockHolder wholder(rwlock_);
906 auto it = propertyWatches_.begin();
907 while (it != propertyWatches_.end()) {
908 const auto &pw = *it;
909 if (pw.GetClassId() == classId && pw.GetFieldId() == fieldId && pw.GetType() == type) {
910 propertyWatches_.erase(it);
911 return true;
912 }
913
914 it++;
915 }
916
917 return false;
918 }
919
920 template <class VRegRef>
GetVRegValue(VRegRef reg)921 static uint64_t GetVRegValue(VRegRef reg)
922 {
923 return reg.HasObject() ? reinterpret_cast<uintptr_t>(reg.GetReference()) : reg.GetLong();
924 }
925
926 template <class VRegRef>
GetVRegKind(VRegRef reg)927 static PtFrame::RegisterKind GetVRegKind([[maybe_unused]] VRegRef reg)
928 {
929 if constexpr (std::is_same<VRegRef, interpreter::DynamicVRegisterRef>::value) {
930 return PtFrame::RegisterKind::TAGGED;
931 } else {
932 return reg.HasObject() ? PtFrame::RegisterKind::REFERENCE : PtFrame::RegisterKind::PRIMITIVE;
933 }
934 }
935
936 template <class FrameHandler>
FillRegisters(Frame * interpreterFrame,PandaVector<uint64_t> & vregs,PandaVector<PtFrame::RegisterKind> & vregKinds,size_t nregs,PandaVector<uint64_t> & args,PandaVector<PtFrame::RegisterKind> & argKinds,size_t nargs,uint64_t & acc,PtFrame::RegisterKind & accKind)937 static void FillRegisters(Frame *interpreterFrame, PandaVector<uint64_t> &vregs,
938 PandaVector<PtFrame::RegisterKind> &vregKinds, size_t nregs, PandaVector<uint64_t> &args,
939 PandaVector<PtFrame::RegisterKind> &argKinds, size_t nargs, uint64_t &acc,
940 PtFrame::RegisterKind &accKind)
941 {
942 FrameHandler frameHandler(interpreterFrame);
943
944 vregs.reserve(nregs);
945 vregKinds.reserve(nregs);
946 for (size_t i = 0; i < nregs; i++) {
947 auto vregReg = frameHandler.GetVReg(i);
948 vregs.push_back(GetVRegValue(vregReg));
949 vregKinds.push_back(GetVRegKind(vregReg));
950 }
951
952 args.reserve(nargs);
953 argKinds.reserve(nargs);
954 for (size_t i = 0; i < nargs; i++) {
955 auto argReg = frameHandler.GetVReg(i + nregs);
956 args.push_back(GetVRegValue(argReg));
957 argKinds.push_back(GetVRegKind(argReg));
958 }
959
960 auto accReg = frameHandler.GetAccAsVReg();
961 acc = GetVRegValue(accReg);
962 accKind = GetVRegKind(accReg);
963 }
964
965 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
PtDebugFrame(Method * method,Frame * interpreterFrame)966 PtDebugFrame::PtDebugFrame(Method *method, Frame *interpreterFrame)
967 : method_(method), methodId_(method->GetFileId()), pandaFile_(method->GetPandaFile()->GetFilename())
968 {
969 isInterpreterFrame_ = interpreterFrame != nullptr;
970 if (!isInterpreterFrame_) {
971 return;
972 }
973
974 size_t nregs = method->GetNumVregs();
975 size_t nargs = method->GetNumArgs();
976 if (interpreterFrame->IsDynamic()) {
977 FillRegisters<DynamicFrameHandler>(interpreterFrame, vregs_, vregKinds_, nregs, args_, argKinds_, nargs, acc_,
978 accKind_);
979 } else {
980 FillRegisters<StaticFrameHandler>(interpreterFrame, vregs_, vregKinds_, nregs, args_, argKinds_, nargs, acc_,
981 accKind_);
982 }
983
984 bcOffset_ = interpreterFrame->GetBytecodeOffset();
985 }
986
987 } // namespace ark::tooling
988