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