1 /*
2 * Copyright (c) 2021-2022 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 "agent/debugger_impl.h"
17
18 #include "base/pt_base64.h"
19 #include "base/pt_events.h"
20 #include "base/pt_params.h"
21 #include "base/pt_returns.h"
22 #include "base/pt_types.h"
23 #include "backend/debugger_executor.h"
24 #include "dispatcher.h"
25 #include "protocol_handler.h"
26
27 #include "ecmascript/jspandafile/js_pandafile_manager.h"
28 #include "ecmascript/napi/jsnapi_helper.h"
29 namespace panda::ecmascript::tooling {
30 using namespace std::placeholders;
31
32 using ObjectType = RemoteObject::TypeName;
33 using ObjectSubType = RemoteObject::SubTypeName;
34 using ObjectClassName = RemoteObject::ClassName;
35 using StepperType = SingleStepper::Type;
36
37 #ifdef OHOS_UNIT_TEST
38 const std::string DATA_APP_PATH = "/";
39 #else
40 const std::string DATA_APP_PATH = "/data/";
41 #endif
42
DebuggerImpl(const EcmaVM * vm,ProtocolChannel * channel,RuntimeImpl * runtime)43 DebuggerImpl::DebuggerImpl(const EcmaVM *vm, ProtocolChannel *channel, RuntimeImpl *runtime)
44 : vm_(vm), frontend_(channel), runtime_(runtime)
45 {
46 hooks_ = std::make_unique<JSPtHooks>(this);
47
48 jsDebugger_ = DebuggerApi::CreateJSDebugger(vm_);
49 DebuggerApi::RegisterHooks(jsDebugger_, hooks_.get());
50
51 DebuggerExecutor::Initialize(vm_);
52 updaterFunc_ = std::bind(&DebuggerImpl::UpdateScopeObject, this, _1, _2, _3);
53 stepperFunc_ = std::bind(&DebuggerImpl::ClearSingleStepper, this);
54 vm_->GetJsDebuggerManager()->SetLocalScopeUpdater(&updaterFunc_);
55 vm_->GetJsDebuggerManager()->SetStepperFunc(&stepperFunc_);
56 }
57
~DebuggerImpl()58 DebuggerImpl::~DebuggerImpl()
59 {
60 DebuggerApi::DestroyJSDebugger(jsDebugger_);
61 }
62
NotifyScriptParsed(ScriptId scriptId,const std::string & fileName,std::string_view entryPoint)63 bool DebuggerImpl::NotifyScriptParsed(ScriptId scriptId, const std::string &fileName, std::string_view entryPoint)
64 {
65 #if !defined(PANDA_TARGET_WINDOWS) && !defined(PANDA_TARGET_MACOS) \
66 && !defined(PANDA_TARGET_ANDROID) && !defined(PANDA_TARGET_IOS)
67 if (fileName.substr(0, DATA_APP_PATH.length()) != DATA_APP_PATH) {
68 LOG_DEBUGGER(DEBUG) << "NotifyScriptParsed: unsupport file: " << fileName;
69 return false;
70 }
71 #endif
72
73 const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
74 if (jsPandaFile == nullptr) {
75 LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: unknown file: " << fileName;
76 return false;
77 }
78
79 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
80 if (extractor == nullptr) {
81 LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: Unsupported file: " << fileName;
82 return false;
83 }
84
85 const char *recordName = entryPoint.data();
86 auto mainMethodIndex = panda_file::File::EntityId(jsPandaFile->GetMainMethodIndex(recordName));
87 const std::string &source = extractor->GetSourceCode(mainMethodIndex);
88 const std::string &url = extractor->GetSourceFile(mainMethodIndex);
89 if (source.empty()) {
90 LOG_DEBUGGER(ERROR) << "NotifyScriptParsed: invalid file: " << fileName;
91 return false;
92 }
93 recordNames_[url] = recordName;
94
95 auto scriptFunc = [this](PtScript *script) -> bool {
96 frontend_.ScriptParsed(vm_, *script);
97 return true;
98 };
99 if (MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
100 LOG_DEBUGGER(WARN) << "NotifyScriptParsed: already loaded: " << url;
101 return false;
102 }
103
104 // Notify script parsed event
105 std::unique_ptr<PtScript> script = std::make_unique<PtScript>(scriptId, fileName, url, source);
106
107 frontend_.ScriptParsed(vm_, *script);
108
109 // Store parsed script in map
110 scripts_[script->GetScriptId()] = std::move(script);
111 return true;
112 }
113
NotifySingleStep(const JSPtLocation & location)114 bool DebuggerImpl::NotifySingleStep(const JSPtLocation &location)
115 {
116 if (UNLIKELY(pauseOnNextByteCode_)) {
117 if (IsSkipLine(location)) {
118 return false;
119 }
120 pauseOnNextByteCode_ = false;
121 LOG_DEBUGGER(INFO) << "StepComplete: pause on next bytecode";
122 return true;
123 }
124
125 if (LIKELY(singleStepper_ == nullptr)) {
126 return false;
127 }
128
129 // step not complete
130 if (!singleStepper_->StepComplete(location.GetBytecodeOffset())) {
131 return false;
132 }
133
134 // skip unknown file or special line -1
135 if (IsSkipLine(location)) {
136 return false;
137 }
138
139 singleStepper_.reset();
140 LOG_DEBUGGER(INFO) << "StepComplete: pause on current byte_code";
141 return true;
142 }
143
IsSkipLine(const JSPtLocation & location)144 bool DebuggerImpl::IsSkipLine(const JSPtLocation &location)
145 {
146 DebugInfoExtractor *extractor = nullptr;
147 const auto *jsPandaFile = location.GetJsPandaFile();
148 auto scriptFunc = [this, &extractor, jsPandaFile](PtScript *) -> bool {
149 extractor = GetExtractor(jsPandaFile);
150 return true;
151 };
152
153 // In hot reload scenario, use the base js panda file instead
154 const auto &fileName = DebuggerApi::GetBaseJSPandaFile(vm_, jsPandaFile)->GetJSPandaFileDesc();
155 if (!MatchScripts(scriptFunc, fileName.c_str(), ScriptMatchType::FILE_NAME) || extractor == nullptr) {
156 LOG_DEBUGGER(INFO) << "StepComplete: skip unknown file " << fileName.c_str();
157 return true;
158 }
159
160 auto callbackFunc = [](int32_t line) -> bool {
161 return line == DebugInfoExtractor::SPECIAL_LINE_MARK;
162 };
163 panda_file::File::EntityId methodId = location.GetMethodId();
164 uint32_t offset = location.GetBytecodeOffset();
165 if (extractor->MatchLineWithOffset(callbackFunc, methodId, offset)) {
166 LOG_DEBUGGER(INFO) << "StepComplete: skip -1";
167 return true;
168 }
169
170 return false;
171 }
172
CheckPauseOnException()173 bool DebuggerImpl::CheckPauseOnException()
174 {
175 if (pauseOnException_ == PauseOnExceptionsState::NONE) {
176 return false;
177 }
178 if (pauseOnException_ == PauseOnExceptionsState::UNCAUGHT) {
179 if (DebuggerApi::IsExceptionCaught(vm_)) {
180 return false;
181 }
182 }
183 return true;
184 }
185
NotifyPaused(std::optional<JSPtLocation> location,PauseReason reason)186 void DebuggerImpl::NotifyPaused(std::optional<JSPtLocation> location, PauseReason reason)
187 {
188 if (reason == EXCEPTION && !CheckPauseOnException()) {
189 return;
190 }
191
192 Local<JSValueRef> exception = DebuggerApi::GetAndClearException(vm_);
193
194 std::vector<std::string> hitBreakpoints;
195 if (location.has_value()) {
196 BreakpointDetails detail;
197 DebugInfoExtractor *extractor = nullptr;
198 auto scriptFunc = [this, &location, &detail, &extractor](PtScript *script) -> bool {
199 detail.url_ = script->GetUrl();
200 extractor = GetExtractor(location->GetJsPandaFile());
201 return true;
202 };
203 auto callbackLineFunc = [&detail](int32_t line) -> bool {
204 detail.line_ = line;
205 return true;
206 };
207 auto callbackColumnFunc = [&detail](int32_t column) -> bool {
208 detail.column_ = column;
209 return true;
210 };
211 panda_file::File::EntityId methodId = location->GetMethodId();
212 uint32_t offset = location->GetBytecodeOffset();
213 // In merge abc scenario, need to use the source file to match to get right url
214 if (!MatchScripts(scriptFunc, location->GetSourceFile(), ScriptMatchType::URL) ||
215 extractor == nullptr || !extractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) ||
216 !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
217 LOG_DEBUGGER(ERROR) << "NotifyPaused: unknown file " << location->GetSourceFile();
218 return;
219 }
220 hitBreakpoints.emplace_back(BreakpointDetails::ToString(detail));
221 }
222
223 // Do something cleaning on paused
224 CleanUpOnPaused();
225
226 // Notify paused event
227 std::vector<std::unique_ptr<CallFrame>> callFrames;
228 if (!GenerateCallFrames(&callFrames)) {
229 LOG_DEBUGGER(ERROR) << "NotifyPaused: GenerateCallFrames failed";
230 return;
231 }
232 tooling::Paused paused;
233 paused.SetCallFrames(std::move(callFrames)).SetReason(reason).SetHitBreakpoints(std::move(hitBreakpoints));
234 if (reason == EXCEPTION && exception->IsError()) {
235 std::unique_ptr<RemoteObject> tmpException = RemoteObject::FromTagged(vm_, exception);
236 paused.SetData(std::move(tmpException));
237 }
238 frontend_.Paused(vm_, paused);
239 if (reason != BREAK_ON_START) {
240 singleStepper_.reset();
241 }
242 debuggerState_ = DebuggerState::PAUSED;
243 frontend_.WaitForDebugger(vm_);
244 DebuggerApi::SetException(vm_, exception);
245 }
246
NotifyNativeCalling(const void * nativeAddress)247 void DebuggerImpl::NotifyNativeCalling(const void *nativeAddress)
248 {
249 tooling::NativeCalling nativeCalling;
250 if (singleStepper_ != nullptr &&
251 singleStepper_->GetStepperType() == StepperType::STEP_INTO) {
252 nativeCalling.SetNativeAddress(nativeAddress);
253 nativeCalling.SetIntoStatus(true);
254 }
255
256 nativePointer_ = DebuggerApi::GetNativePointer(vm_);
257 nativeCalling.SetNativePointer(nativePointer_);
258 std::vector<std::unique_ptr<CallFrame>> callFrames;
259 if (GenerateCallFrames(&callFrames)) {
260 nativeCalling.SetCallFrames(std::move(callFrames));
261 }
262 frontend_.NativeCalling(vm_, nativeCalling);
263 frontend_.WaitForDebugger(vm_);
264 }
265
266 // only use for test case
SetDebuggerState(DebuggerState debuggerState)267 void DebuggerImpl::SetDebuggerState(DebuggerState debuggerState)
268 {
269 debuggerState_ = debuggerState;
270 }
271
NotifyHandleProtocolCommand()272 void DebuggerImpl::NotifyHandleProtocolCommand()
273 {
274 auto *handler = vm_->GetJsDebuggerManager()->GetDebuggerHandler();
275 handler->ProcessCommand();
276 }
277
Dispatch(const DispatchRequest & request)278 void DebuggerImpl::DispatcherImpl::Dispatch(const DispatchRequest &request)
279 {
280 static std::unordered_map<std::string, AgentHandler> dispatcherTable {
281 { "enable", &DebuggerImpl::DispatcherImpl::Enable },
282 { "disable", &DebuggerImpl::DispatcherImpl::Disable },
283 { "evaluateOnCallFrame", &DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame },
284 { "getPossibleBreakpoints", &DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints },
285 { "getScriptSource", &DebuggerImpl::DispatcherImpl::GetScriptSource },
286 { "pause", &DebuggerImpl::DispatcherImpl::Pause },
287 { "removeBreakpoint", &DebuggerImpl::DispatcherImpl::RemoveBreakpoint },
288 { "resume", &DebuggerImpl::DispatcherImpl::Resume },
289 { "setAsyncCallStackDepth", &DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth },
290 { "setBreakpointByUrl", &DebuggerImpl::DispatcherImpl::SetBreakpointByUrl },
291 { "setPauseOnExceptions", &DebuggerImpl::DispatcherImpl::SetPauseOnExceptions },
292 { "stepInto", &DebuggerImpl::DispatcherImpl::StepInto },
293 { "stepOut", &DebuggerImpl::DispatcherImpl::StepOut },
294 { "stepOver", &DebuggerImpl::DispatcherImpl::StepOver },
295 { "setMixedDebugEnabled", &DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled },
296 { "setBlackboxPatterns", &DebuggerImpl::DispatcherImpl::SetBlackboxPatterns },
297 { "replyNativeCalling", &DebuggerImpl::DispatcherImpl::ReplyNativeCalling },
298 { "getPossibleAndSetBreakpointByUrl", &DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl },
299 { "dropFrame", &DebuggerImpl::DispatcherImpl::DropFrame }
300 };
301
302 const std::string &method = request.GetMethod();
303 LOG_DEBUGGER(DEBUG) << "dispatch [" << method << "] to DebuggerImpl";
304 auto entry = dispatcherTable.find(method);
305 if (entry != dispatcherTable.end() && entry->second != nullptr) {
306 (this->*(entry->second))(request);
307 } else {
308 SendResponse(request, DispatchResponse::Fail("Unknown method: " + method));
309 }
310 }
311
Enable(const DispatchRequest & request)312 void DebuggerImpl::DispatcherImpl::Enable(const DispatchRequest &request)
313 {
314 std::unique_ptr<EnableParams> params = EnableParams::Create(request.GetParams());
315 if (params == nullptr) {
316 SendResponse(request, DispatchResponse::Fail("wrong params"));
317 return;
318 }
319
320 UniqueDebuggerId id;
321 DispatchResponse response = debugger_->Enable(*params, &id);
322
323 EnableReturns result(id);
324 SendResponse(request, response, result);
325 }
326
Disable(const DispatchRequest & request)327 void DebuggerImpl::DispatcherImpl::Disable(const DispatchRequest &request)
328 {
329 DispatchResponse response = debugger_->Disable();
330 SendResponse(request, response);
331 }
332
EvaluateOnCallFrame(const DispatchRequest & request)333 void DebuggerImpl::DispatcherImpl::EvaluateOnCallFrame(const DispatchRequest &request)
334 {
335 std::unique_ptr<EvaluateOnCallFrameParams> params = EvaluateOnCallFrameParams::Create(request.GetParams());
336 if (params == nullptr) {
337 SendResponse(request, DispatchResponse::Fail("wrong params"));
338 return;
339 }
340 std::unique_ptr<RemoteObject> result1;
341 DispatchResponse response = debugger_->EvaluateOnCallFrame(*params, &result1);
342 if (result1 == nullptr) {
343 SendResponse(request, response);
344 return;
345 }
346
347 EvaluateOnCallFrameReturns result(std::move(result1));
348 SendResponse(request, response, result);
349 }
350
GetPossibleBreakpoints(const DispatchRequest & request)351 void DebuggerImpl::DispatcherImpl::GetPossibleBreakpoints(const DispatchRequest &request)
352 {
353 std::unique_ptr<GetPossibleBreakpointsParams> params = GetPossibleBreakpointsParams::Create(request.GetParams());
354 if (params == nullptr) {
355 SendResponse(request, DispatchResponse::Fail("wrong params"));
356 return;
357 }
358 std::vector<std::unique_ptr<BreakLocation>> locations;
359 DispatchResponse response = debugger_->GetPossibleBreakpoints(*params, &locations);
360 GetPossibleBreakpointsReturns result(std::move(locations));
361 SendResponse(request, response, result);
362 }
363
GetScriptSource(const DispatchRequest & request)364 void DebuggerImpl::DispatcherImpl::GetScriptSource(const DispatchRequest &request)
365 {
366 std::unique_ptr<GetScriptSourceParams> params = GetScriptSourceParams::Create(request.GetParams());
367 if (params == nullptr) {
368 SendResponse(request, DispatchResponse::Fail("wrong params"));
369 return;
370 }
371 std::string source;
372 DispatchResponse response = debugger_->GetScriptSource(*params, &source);
373 GetScriptSourceReturns result(source);
374 SendResponse(request, response, result);
375 }
376
Pause(const DispatchRequest & request)377 void DebuggerImpl::DispatcherImpl::Pause(const DispatchRequest &request)
378 {
379 DispatchResponse response = debugger_->Pause();
380 SendResponse(request, response);
381 }
382
RemoveBreakpoint(const DispatchRequest & request)383 void DebuggerImpl::DispatcherImpl::RemoveBreakpoint(const DispatchRequest &request)
384 {
385 std::unique_ptr<RemoveBreakpointParams> params = RemoveBreakpointParams::Create(request.GetParams());
386 if (params == nullptr) {
387 SendResponse(request, DispatchResponse::Fail("wrong params"));
388 return;
389 }
390 DispatchResponse response = debugger_->RemoveBreakpoint(*params);
391 SendResponse(request, response);
392 }
393
Resume(const DispatchRequest & request)394 void DebuggerImpl::DispatcherImpl::Resume(const DispatchRequest &request)
395 {
396 std::unique_ptr<ResumeParams> params = ResumeParams::Create(request.GetParams());
397 if (params == nullptr) {
398 SendResponse(request, DispatchResponse::Fail("wrong params"));
399 return;
400 }
401 DispatchResponse response = debugger_->Resume(*params);
402 SendResponse(request, response);
403 }
404
SetAsyncCallStackDepth(const DispatchRequest & request)405 void DebuggerImpl::DispatcherImpl::SetAsyncCallStackDepth(const DispatchRequest &request)
406 {
407 DispatchResponse response = debugger_->SetAsyncCallStackDepth();
408 SendResponse(request, response);
409 }
410
SetBreakpointByUrl(const DispatchRequest & request)411 void DebuggerImpl::DispatcherImpl::SetBreakpointByUrl(const DispatchRequest &request)
412 {
413 std::unique_ptr<SetBreakpointByUrlParams> params = SetBreakpointByUrlParams::Create(request.GetParams());
414 if (params == nullptr) {
415 SendResponse(request, DispatchResponse::Fail("wrong params"));
416 return;
417 }
418
419 std::string outId;
420 std::vector<std::unique_ptr<Location>> outLocations;
421 DispatchResponse response = debugger_->SetBreakpointByUrl(*params, &outId, &outLocations);
422 SetBreakpointByUrlReturns result(outId, std::move(outLocations));
423 SendResponse(request, response, result);
424 }
425
GetPossibleAndSetBreakpointByUrl(const DispatchRequest & request)426 void DebuggerImpl::DispatcherImpl::GetPossibleAndSetBreakpointByUrl(const DispatchRequest &request)
427 {
428 std::unique_ptr<GetPossibleAndSetBreakpointParams> params;
429 params = GetPossibleAndSetBreakpointParams::Create(request.GetParams());
430 if (params == nullptr) {
431 SendResponse(request, DispatchResponse::Fail("wrong params"));
432 return;
433 }
434
435 std::vector<std::unique_ptr<BreakpointReturnInfo>> outLoc;
436 DispatchResponse response = debugger_->GetPossibleAndSetBreakpointByUrl(*params, outLoc);
437 GetPossibleAndSetBreakpointByUrlReturns result(std::move(outLoc));
438 SendResponse(request, response, result);
439 }
440
SetPauseOnExceptions(const DispatchRequest & request)441 void DebuggerImpl::DispatcherImpl::SetPauseOnExceptions(const DispatchRequest &request)
442 {
443 std::unique_ptr<SetPauseOnExceptionsParams> params = SetPauseOnExceptionsParams::Create(request.GetParams());
444 if (params == nullptr) {
445 SendResponse(request, DispatchResponse::Fail("wrong params"));
446 return;
447 }
448
449 DispatchResponse response = debugger_->SetPauseOnExceptions(*params);
450 SendResponse(request, response);
451 }
452
StepInto(const DispatchRequest & request)453 void DebuggerImpl::DispatcherImpl::StepInto(const DispatchRequest &request)
454 {
455 std::unique_ptr<StepIntoParams> params = StepIntoParams::Create(request.GetParams());
456 if (params == nullptr) {
457 SendResponse(request, DispatchResponse::Fail("wrong params"));
458 return;
459 }
460 DispatchResponse response = debugger_->StepInto(*params);
461 SendResponse(request, response);
462 }
463
StepOut(const DispatchRequest & request)464 void DebuggerImpl::DispatcherImpl::StepOut(const DispatchRequest &request)
465 {
466 DispatchResponse response = debugger_->StepOut();
467 SendResponse(request, response);
468 }
469
StepOver(const DispatchRequest & request)470 void DebuggerImpl::DispatcherImpl::StepOver(const DispatchRequest &request)
471 {
472 std::unique_ptr<StepOverParams> params = StepOverParams::Create(request.GetParams());
473 if (params == nullptr) {
474 SendResponse(request, DispatchResponse::Fail("wrong params"));
475 return;
476 }
477 DispatchResponse response = debugger_->StepOver(*params);
478 SendResponse(request, response);
479 }
480
SetMixedDebugEnabled(const DispatchRequest & request)481 void DebuggerImpl::DispatcherImpl::SetMixedDebugEnabled(const DispatchRequest &request)
482 {
483 std::unique_ptr<SetMixedDebugParams> params = SetMixedDebugParams::Create(request.GetParams());
484 if (params == nullptr) {
485 SendResponse(request, DispatchResponse::Fail("wrong params"));
486 return;
487 }
488 DispatchResponse response = debugger_->SetMixedDebugEnabled(*params);
489 SendResponse(request, response);
490 }
491
ReplyNativeCalling(const DispatchRequest & request)492 void DebuggerImpl::DispatcherImpl::ReplyNativeCalling(const DispatchRequest &request)
493 {
494 std::unique_ptr<ReplyNativeCallingParams> params = ReplyNativeCallingParams::Create(request.GetParams());
495 if (params == nullptr) {
496 SendResponse(request, DispatchResponse::Fail("wrong params"));
497 return;
498 }
499 DispatchResponse response = debugger_->ReplyNativeCalling(*params);
500 SendResponse(request, response);
501 }
502
SetBlackboxPatterns(const DispatchRequest & request)503 void DebuggerImpl::DispatcherImpl::SetBlackboxPatterns(const DispatchRequest &request)
504 {
505 DispatchResponse response = debugger_->SetBlackboxPatterns();
506 SendResponse(request, response);
507 }
508
DropFrame(const DispatchRequest & request)509 void DebuggerImpl::DispatcherImpl::DropFrame(const DispatchRequest &request)
510 {
511 std::unique_ptr<DropFrameParams> params = DropFrameParams::Create(request.GetParams());
512 if (params == nullptr) {
513 SendResponse(request, DispatchResponse::Fail("wrong params"));
514 return;
515 }
516 DispatchResponse response = debugger_->DropFrame(*params);
517 SendResponse(request, response);
518 }
519
AllowNotify(const EcmaVM * vm) const520 bool DebuggerImpl::Frontend::AllowNotify(const EcmaVM *vm) const
521 {
522 return vm->GetJsDebuggerManager()->IsDebugMode() && channel_ != nullptr;
523 }
524
BreakpointResolved(const EcmaVM * vm)525 void DebuggerImpl::Frontend::BreakpointResolved(const EcmaVM *vm)
526 {
527 if (!AllowNotify(vm)) {
528 return;
529 }
530
531 tooling::BreakpointResolved breakpointResolved;
532 channel_->SendNotification(breakpointResolved);
533 }
534
Paused(const EcmaVM * vm,const tooling::Paused & paused)535 void DebuggerImpl::Frontend::Paused(const EcmaVM *vm, const tooling::Paused &paused)
536 {
537 if (!AllowNotify(vm)) {
538 return;
539 }
540
541 channel_->SendNotification(paused);
542 }
543
NativeCalling(const EcmaVM * vm,const tooling::NativeCalling & nativeCalling)544 void DebuggerImpl::Frontend::NativeCalling(const EcmaVM *vm, const tooling::NativeCalling &nativeCalling)
545 {
546 if (!AllowNotify(vm)) {
547 return;
548 }
549
550 channel_->SendNotification(nativeCalling);
551 }
552
Resumed(const EcmaVM * vm)553 void DebuggerImpl::Frontend::Resumed(const EcmaVM *vm)
554 {
555 if (!AllowNotify(vm)) {
556 return;
557 }
558
559 channel_->RunIfWaitingForDebugger();
560 tooling::Resumed resumed;
561 channel_->SendNotification(resumed);
562 }
563
ScriptFailedToParse(const EcmaVM * vm)564 void DebuggerImpl::Frontend::ScriptFailedToParse(const EcmaVM *vm)
565 {
566 if (!AllowNotify(vm)) {
567 return;
568 }
569
570 tooling::ScriptFailedToParse scriptFailedToParse;
571 channel_->SendNotification(scriptFailedToParse);
572 }
573
ScriptParsed(const EcmaVM * vm,const PtScript & script)574 void DebuggerImpl::Frontend::ScriptParsed(const EcmaVM *vm, const PtScript &script)
575 {
576 if (!AllowNotify(vm)) {
577 return;
578 }
579
580 tooling::ScriptParsed scriptParsed;
581 scriptParsed.SetScriptId(script.GetScriptId())
582 .SetUrl(script.GetUrl())
583 .SetStartLine(0)
584 .SetStartColumn(0)
585 .SetEndLine(script.GetEndLine())
586 .SetEndColumn(0)
587 .SetExecutionContextId(0)
588 .SetHash(script.GetHash());
589
590 channel_->SendNotification(scriptParsed);
591 }
592
WaitForDebugger(const EcmaVM * vm)593 void DebuggerImpl::Frontend::WaitForDebugger(const EcmaVM *vm)
594 {
595 if (!AllowNotify(vm)) {
596 return;
597 }
598
599 channel_->WaitForDebugger();
600 }
601
RunIfWaitingForDebugger(const EcmaVM * vm)602 void DebuggerImpl::Frontend::RunIfWaitingForDebugger(const EcmaVM *vm)
603 {
604 if (!AllowNotify(vm)) {
605 return;
606 }
607
608 channel_->RunIfWaitingForDebugger();
609 }
610
Enable(const EnableParams & params,UniqueDebuggerId * id)611 DispatchResponse DebuggerImpl::Enable([[maybe_unused]] const EnableParams ¶ms, UniqueDebuggerId *id)
612 {
613 ASSERT(id != nullptr);
614 *id = 0;
615 vm_->GetJsDebuggerManager()->SetDebugMode(true);
616 for (auto &script : scripts_) {
617 frontend_.ScriptParsed(vm_, *script.second);
618 }
619 debuggerState_ = DebuggerState::ENABLED;
620 return DispatchResponse::Ok();
621 }
622
Disable()623 DispatchResponse DebuggerImpl::Disable()
624 {
625 DebuggerApi::RemoveAllBreakpoints(jsDebugger_);
626 frontend_.RunIfWaitingForDebugger(vm_);
627 frontend_.Resumed(vm_);
628 vm_->GetJsDebuggerManager()->SetDebugMode(false);
629 debuggerState_ = DebuggerState::DISABLED;
630 return DispatchResponse::Ok();
631 }
632
EvaluateOnCallFrame(const EvaluateOnCallFrameParams & params,std::unique_ptr<RemoteObject> * result)633 DispatchResponse DebuggerImpl::EvaluateOnCallFrame(const EvaluateOnCallFrameParams ¶ms,
634 std::unique_ptr<RemoteObject> *result)
635 {
636 CallFrameId callFrameId = params.GetCallFrameId();
637 const std::string &expression = params.GetExpression();
638 if (callFrameId < 0 || callFrameId >= static_cast<CallFrameId>(callFrameHandlers_.size())) {
639 return DispatchResponse::Fail("Invalid callFrameId.");
640 }
641
642 std::vector<uint8_t> dest;
643 if (!DecodeAndCheckBase64(expression, dest)) {
644 LOG_DEBUGGER(ERROR) << "EvaluateValue: base64 decode failed";
645 auto ret = CmptEvaluateValue(callFrameId, expression, result);
646 if (ret.has_value()) {
647 LOG_DEBUGGER(ERROR) << "Evaluate fail, expression: " << expression;
648 }
649 return DispatchResponse::Create(ret);
650 }
651
652 auto funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
653 JSPandaFile::ENTRY_FUNCTION_NAME);
654 auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(vm_), funcRef,
655 callFrameHandlers_[callFrameId]);
656 if (vm_->GetJSThread()->HasPendingException()) {
657 LOG_DEBUGGER(ERROR) << "EvaluateValue: has pending exception";
658 std::string msg;
659 DebuggerApi::HandleUncaughtException(vm_, msg);
660 *result = RemoteObject::FromTagged(vm_,
661 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, msg.data())));
662 return DispatchResponse::Fail(msg);
663 }
664
665 *result = RemoteObject::FromTagged(vm_, res);
666 runtime_->CacheObjectIfNeeded(res, (*result).get());
667 return DispatchResponse::Ok();
668 }
669
GetPossibleBreakpoints(const GetPossibleBreakpointsParams & params,std::vector<std::unique_ptr<BreakLocation>> * locations)670 DispatchResponse DebuggerImpl::GetPossibleBreakpoints(const GetPossibleBreakpointsParams ¶ms,
671 std::vector<std::unique_ptr<BreakLocation>> *locations)
672 {
673 Location *start = params.GetStart();
674 auto iter = scripts_.find(start->GetScriptId());
675 if (iter == scripts_.end()) {
676 return DispatchResponse::Fail("Unknown file name.");
677 }
678 const std::string &url = iter->second->GetUrl();
679 DebugInfoExtractor *extractor = GetExtractor(url);
680 if (extractor == nullptr) {
681 LOG_DEBUGGER(ERROR) << "GetPossibleBreakpoints: extractor is null";
682 return DispatchResponse::Fail("Unknown file name.");
683 }
684
685 int32_t line = start->GetLine();
686 int32_t column = start->GetColumn();
687 auto callbackFunc = [](const JSPtLocation &) -> bool {
688 return true;
689 };
690 if (extractor->MatchWithLocation(callbackFunc, line, column, url, GetRecordName(url))) {
691 std::unique_ptr<BreakLocation> location = std::make_unique<BreakLocation>();
692 location->SetScriptId(start->GetScriptId()).SetLine(line).SetColumn(column);
693 locations->emplace_back(std::move(location));
694 }
695 return DispatchResponse::Ok();
696 }
697
GetScriptSource(const GetScriptSourceParams & params,std::string * source)698 DispatchResponse DebuggerImpl::GetScriptSource(const GetScriptSourceParams ¶ms, std::string *source)
699 {
700 ScriptId scriptId = params.GetScriptId();
701 auto iter = scripts_.find(scriptId);
702 if (iter == scripts_.end()) {
703 *source = "";
704 return DispatchResponse::Fail("unknown script id: " + std::to_string(scriptId));
705 }
706 *source = iter->second->GetScriptSource();
707
708 return DispatchResponse::Ok();
709 }
710
Pause()711 DispatchResponse DebuggerImpl::Pause()
712 {
713 pauseOnNextByteCode_ = true;
714 return DispatchResponse::Ok();
715 }
716
RemoveBreakpoint(const RemoveBreakpointParams & params)717 DispatchResponse DebuggerImpl::RemoveBreakpoint(const RemoveBreakpointParams ¶ms)
718 {
719 std::string id = params.GetBreakpointId();
720 LOG_DEBUGGER(INFO) << "RemoveBreakpoint: " << id;
721 BreakpointDetails metaData{};
722 if (!BreakpointDetails::ParseBreakpointId(id, &metaData)) {
723 return DispatchResponse::Fail("Parse breakpoint id failed");
724 }
725 DebugInfoExtractor *extractor = GetExtractor(metaData.url_);
726 if (extractor == nullptr) {
727 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: extractor is null";
728 return DispatchResponse::Fail("Unknown file name.");
729 }
730
731 auto scriptFunc = [](PtScript *) -> bool {
732 return true;
733 };
734 if (!MatchScripts(scriptFunc, metaData.url_, ScriptMatchType::URL)) {
735 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Unknown url: " << metaData.url_;
736 return DispatchResponse::Fail("Unknown file name.");
737 }
738
739 auto callbackFunc = [this](const JSPtLocation &location) -> bool {
740 LOG_DEBUGGER(INFO) << "remove breakpoint location: " << location.ToString();
741 return DebuggerApi::RemoveBreakpoint(jsDebugger_, location);
742 };
743 if (!extractor->MatchWithLocation(callbackFunc, metaData.line_, metaData.column_,
744 metaData.url_, GetRecordName(metaData.url_))) {
745 LOG_DEBUGGER(ERROR) << "failed to remove breakpoint location number: "
746 << metaData.line_ << ":" << metaData.column_;
747 return DispatchResponse::Fail("Breakpoint not found.");
748 }
749
750 LOG_DEBUGGER(INFO) << "remove breakpoint line number:" << metaData.line_;
751 return DispatchResponse::Ok();
752 }
753
Resume(const ResumeParams & params)754 DispatchResponse DebuggerImpl::Resume([[maybe_unused]] const ResumeParams ¶ms)
755 {
756 if (debuggerState_ != DebuggerState::PAUSED) {
757 return DispatchResponse::Fail("Can only perform operation while paused");
758 }
759 frontend_.Resumed(vm_);
760 debuggerState_ = DebuggerState::ENABLED;
761 return DispatchResponse::Ok();
762 }
763
SetAsyncCallStackDepth()764 DispatchResponse DebuggerImpl::SetAsyncCallStackDepth()
765 {
766 return DispatchResponse::Fail("SetAsyncCallStackDepth not support now");
767 }
768
SetBreakpointByUrl(const SetBreakpointByUrlParams & params,std::string * outId,std::vector<std::unique_ptr<Location>> * outLocations)769 DispatchResponse DebuggerImpl::SetBreakpointByUrl(const SetBreakpointByUrlParams ¶ms,
770 std::string *outId,
771 std::vector<std::unique_ptr<Location>> *outLocations)
772 {
773 if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
774 return DispatchResponse::Fail("SetBreakpointByUrl: debugger agent is not enabled");
775 }
776 const std::string &url = params.GetUrl();
777 int32_t lineNumber = params.GetLine();
778 int32_t columnNumber = params.GetColumn();
779 auto condition = params.HasCondition() ? params.GetCondition() : std::optional<std::string> {};
780
781 DebugInfoExtractor *extractor = GetExtractor(url);
782 if (extractor == nullptr) {
783 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: extractor is null";
784 return DispatchResponse::Fail("Unknown file name.");
785 }
786
787 ScriptId scriptId;
788 std::string fileName;
789 auto scriptFunc = [&scriptId, &fileName](PtScript *script) -> bool {
790 scriptId = script->GetScriptId();
791 fileName = script->GetFileName();
792 return true;
793 };
794 if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
795 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: Unknown url: " << url;
796 return DispatchResponse::Fail("Unknown file name.");
797 }
798
799 auto callbackFunc = [this, &condition](const JSPtLocation &location) -> bool {
800 LOG_DEBUGGER(INFO) << "set breakpoint location: " << location.ToString();
801 Local<FunctionRef> condFuncRef = FunctionRef::Undefined(vm_);
802 if (condition.has_value() && !condition.value().empty()) {
803 std::vector<uint8_t> dest;
804 if (!DecodeAndCheckBase64(condition.value(), dest)) {
805 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: base64 decode failed";
806 return false;
807 }
808 condFuncRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(),
809 JSPandaFile::ENTRY_FUNCTION_NAME);
810 if (condFuncRef->IsUndefined()) {
811 LOG_DEBUGGER(ERROR) << "SetBreakpointByUrl: generate function failed";
812 return false;
813 }
814 }
815 return DebuggerApi::SetBreakpoint(jsDebugger_, location, condFuncRef);
816 };
817 if (!extractor->MatchWithLocation(callbackFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
818 LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
819 return DispatchResponse::Fail("Breakpoint not found.");
820 }
821
822 BreakpointDetails metaData{lineNumber, 0, url};
823 *outId = BreakpointDetails::ToString(metaData);
824 *outLocations = std::vector<std::unique_ptr<Location>>();
825 std::unique_ptr<Location> location = std::make_unique<Location>();
826 location->SetScriptId(scriptId).SetLine(lineNumber).SetColumn(0);
827 outLocations->emplace_back(std::move(location));
828
829 return DispatchResponse::Ok();
830 }
831
GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams & params,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)832 DispatchResponse DebuggerImpl::GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams ¶ms,
833 std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
834 {
835 if (!vm_->GetJsDebuggerManager()->IsDebugMode()) {
836 return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: debugger agent is not enabled");
837 }
838 if (!params.HasBreakpointsList()) {
839 return DispatchResponse::Fail("GetPossibleAndSetBreakpointByUrl: no pennding breakpoint exists");
840 }
841 auto breakpointList = params.GetBreakpointsList();
842 for (const auto &breakpoint : *breakpointList) {
843 bool isProcessSucceed = ProcessSingleBreakpoint(*breakpoint, outLocations);
844 if (!isProcessSucceed) {
845 std::string invalidBpId = "invalid";
846 std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
847 bpInfo->SetId(invalidBpId)
848 .SetLineNumber(breakpoint->GetLineNumber())
849 .SetColumnNumber(breakpoint->GetColumnNumber());
850 outLocations.emplace_back(std::move(bpInfo));
851 }
852 }
853 return DispatchResponse::Ok();
854 }
855
ProcessSingleBreakpoint(const BreakpointInfo & breakpoint,std::vector<std::unique_ptr<BreakpointReturnInfo>> & outLocations)856 bool DebuggerImpl::ProcessSingleBreakpoint(const BreakpointInfo &breakpoint,
857 std::vector<std::unique_ptr<BreakpointReturnInfo>> &outLocations)
858 {
859 const std::string &url = breakpoint.GetUrl();
860 int32_t lineNumber = breakpoint.GetLineNumber();
861 int32_t columnNumber = breakpoint.GetColumnNumber();
862 auto condition = breakpoint.HasCondition() ? breakpoint.GetCondition() : std::optional<std::string> {};
863
864 DebugInfoExtractor *extractor = GetExtractor(url);
865 if (extractor == nullptr) {
866 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: extractor is null";
867 return false;
868 }
869
870 ScriptId scriptId;
871 std::string fileName;
872 auto matchScriptCbFunc = [&scriptId, &fileName](PtScript *script) -> bool {
873 scriptId = script->GetScriptId();
874 fileName = script->GetFileName();
875 return true;
876 };
877 if (!MatchScripts(matchScriptCbFunc, url, ScriptMatchType::URL)) {
878 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: unknown Url: " << url;
879 return false;
880 }
881
882 // check breakpoint condition before doing matchWithLocation
883 Local<FunctionRef> funcRef = FunctionRef::Undefined(vm_);
884 if (condition.has_value() && !condition.value().empty()) {
885 std::vector<uint8_t> dest;
886 if (!DecodeAndCheckBase64(condition.value(), dest)) {
887 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: base64 decode failed";
888 return false;
889 }
890 funcRef = DebuggerApi::GenerateFuncFromBuffer(vm_, dest.data(), dest.size(), JSPandaFile::ENTRY_FUNCTION_NAME);
891 if (funcRef->IsUndefined()) {
892 LOG_DEBUGGER(ERROR) << "GetPossibleAndSetBreakpointByUrl: generate condition function failed";
893 return false;
894 }
895 }
896
897 auto matchLocationCbFunc = [this, &funcRef](const JSPtLocation &location) -> bool {
898 return DebuggerApi::SetBreakpoint(jsDebugger_, location, funcRef);
899 };
900 if (!extractor->MatchWithLocation(matchLocationCbFunc, lineNumber, columnNumber, url, GetRecordName(url))) {
901 LOG_DEBUGGER(ERROR) << "failed to set breakpoint location number: " << lineNumber << ":" << columnNumber;
902 return false;
903 }
904
905 BreakpointDetails bpMetaData {lineNumber, columnNumber, url};
906 std::string outId = BreakpointDetails::ToString(bpMetaData);
907 std::unique_ptr<BreakpointReturnInfo> bpInfo = std::make_unique<BreakpointReturnInfo>();
908 bpInfo->SetScriptId(scriptId).SetLineNumber(lineNumber).SetColumnNumber(columnNumber).SetId(outId);
909 outLocations.emplace_back(std::move(bpInfo));
910
911 return true;
912 }
913
SetPauseOnExceptions(const SetPauseOnExceptionsParams & params)914 DispatchResponse DebuggerImpl::SetPauseOnExceptions(const SetPauseOnExceptionsParams ¶ms)
915 {
916 pauseOnException_ = params.GetState();
917 return DispatchResponse::Ok();
918 }
919
StepInto(const StepIntoParams & params)920 DispatchResponse DebuggerImpl::StepInto([[maybe_unused]] const StepIntoParams ¶ms)
921 {
922 if (debuggerState_ != DebuggerState::PAUSED) {
923 return DispatchResponse::Fail("Can only perform operation while paused");
924 }
925 singleStepper_ = SingleStepper::GetStepIntoStepper(vm_);
926 if (singleStepper_ == nullptr) {
927 LOG_DEBUGGER(ERROR) << "StepInto: singleStepper is null";
928 return DispatchResponse::Fail("Failed to StepInto");
929 }
930 frontend_.Resumed(vm_);
931 debuggerState_ = DebuggerState::ENABLED;
932 return DispatchResponse::Ok();
933 }
934
StepOut()935 DispatchResponse DebuggerImpl::StepOut()
936 {
937 if (debuggerState_ != DebuggerState::PAUSED) {
938 return DispatchResponse::Fail("Can only perform operation while paused");
939 }
940 singleStepper_ = SingleStepper::GetStepOutStepper(vm_);
941 if (singleStepper_ == nullptr) {
942 LOG_DEBUGGER(ERROR) << "StepOut: singleStepper is null";
943 return DispatchResponse::Fail("Failed to StepOut");
944 }
945 frontend_.Resumed(vm_);
946 debuggerState_ = DebuggerState::ENABLED;
947 return DispatchResponse::Ok();
948 }
949
StepOver(const StepOverParams & params)950 DispatchResponse DebuggerImpl::StepOver([[maybe_unused]] const StepOverParams ¶ms)
951 {
952 if (debuggerState_ != DebuggerState::PAUSED) {
953 return DispatchResponse::Fail("Can only perform operation while paused");
954 }
955 singleStepper_ = SingleStepper::GetStepOverStepper(vm_);
956 if (singleStepper_ == nullptr) {
957 LOG_DEBUGGER(ERROR) << "StepOver: singleStepper is null";
958 return DispatchResponse::Fail("Failed to StepOver");
959 }
960 frontend_.Resumed(vm_);
961 debuggerState_ = DebuggerState::ENABLED;
962 return DispatchResponse::Ok();
963 }
964
SetBlackboxPatterns()965 DispatchResponse DebuggerImpl::SetBlackboxPatterns()
966 {
967 return DispatchResponse::Fail("SetBlackboxPatterns not support now");
968 }
969
SetMixedDebugEnabled(const SetMixedDebugParams & params)970 DispatchResponse DebuggerImpl::SetMixedDebugEnabled([[maybe_unused]] const SetMixedDebugParams ¶ms)
971 {
972 vm_->GetJsDebuggerManager()->SetMixedDebugEnabled(params.GetEnabled());
973 return DispatchResponse::Ok();
974 }
975
ReplyNativeCalling(const ReplyNativeCallingParams & params)976 DispatchResponse DebuggerImpl::ReplyNativeCalling([[maybe_unused]] const ReplyNativeCallingParams ¶ms)
977 {
978 frontend_.Resumed(vm_);
979 if (params.GetUserCode()) {
980 singleStepper_.reset();
981 }
982 return DispatchResponse::Ok();
983 }
984
DropFrame(const DropFrameParams & params)985 DispatchResponse DebuggerImpl::DropFrame(const DropFrameParams ¶ms)
986 {
987 if (debuggerState_ != DebuggerState::PAUSED) {
988 return DispatchResponse::Fail("Can only perform operation while paused");
989 }
990 uint32_t droppedDepth = 1;
991 if (params.HasDroppedDepth()) {
992 droppedDepth = params.GetDroppedDepth();
993 }
994 uint32_t stackDepth = DebuggerApi::GetStackDepth(vm_);
995 if (droppedDepth > stackDepth) {
996 return DispatchResponse::Fail("The input depth exceeds stackDepth");
997 }
998 if (droppedDepth == stackDepth) {
999 return DispatchResponse::Fail("The bottom frame cannot be dropped");
1000 }
1001 for (uint32_t i = 0; i < droppedDepth; i++) {
1002 DebuggerApi::DropLastFrame(vm_);
1003 }
1004 pauseOnNextByteCode_ = true;
1005 frontend_.RunIfWaitingForDebugger(vm_);
1006 debuggerState_ = DebuggerState::ENABLED;
1007 return DispatchResponse::Ok();
1008 }
1009
CleanUpOnPaused()1010 void DebuggerImpl::CleanUpOnPaused()
1011 {
1012 runtime_->curObjectId_ = 0;
1013 runtime_->properties_.clear();
1014
1015 callFrameHandlers_.clear();
1016 scopeObjects_.clear();
1017 }
1018
Trim(const std::string & str)1019 std::string DebuggerImpl::Trim(const std::string &str)
1020 {
1021 std::string ret = str;
1022 // If ret has only ' ', remove all charactors.
1023 ret.erase(ret.find_last_not_of(' ') + 1);
1024 // If ret has only ' ', remove all charactors.
1025 ret.erase(0, ret.find_first_not_of(' '));
1026 return ret;
1027 }
1028
GetExtractor(const JSPandaFile * jsPandaFile)1029 DebugInfoExtractor *DebuggerImpl::GetExtractor(const JSPandaFile *jsPandaFile)
1030 {
1031 return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1032 }
1033
1034 // mainly used for breakpoints to match location
GetExtractor(const std::string & url)1035 DebugInfoExtractor *DebuggerImpl::GetExtractor(const std::string &url)
1036 {
1037 // match patch file first if it contains diff for the url, and currently only support the file
1038 // specified by the url change as a whole
1039 auto *extractor = DebuggerApi::GetPatchExtractor(vm_, url);
1040 if (extractor != nullptr) {
1041 return extractor;
1042 }
1043
1044 std::string fileName;
1045 auto scriptFunc = [&fileName](const PtScript *script) -> bool {
1046 fileName = script->GetFileName();
1047 return true;
1048 };
1049 if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
1050 return nullptr;
1051 }
1052
1053 const JSPandaFile *jsPandaFile = JSPandaFileManager::GetInstance()->FindJSPandaFile(fileName.c_str()).get();
1054 if (jsPandaFile == nullptr) {
1055 return nullptr;
1056 }
1057 return JSPandaFileManager::GetInstance()->GetJSPtExtractor(jsPandaFile);
1058 }
1059
GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> * callFrames)1060 bool DebuggerImpl::GenerateCallFrames(std::vector<std::unique_ptr<CallFrame>> *callFrames)
1061 {
1062 CallFrameId callFrameId = 0;
1063 auto walkerFunc = [this, &callFrameId, &callFrames](const FrameHandler *frameHandler) -> StackState {
1064 if (DebuggerApi::IsNativeMethod(frameHandler)) {
1065 LOG_DEBUGGER(INFO) << "GenerateCallFrames: Skip CFrame and Native method";
1066 return StackState::CONTINUE;
1067 }
1068 std::unique_ptr<CallFrame> callFrame = std::make_unique<CallFrame>();
1069 if (!GenerateCallFrame(callFrame.get(), frameHandler, callFrameId)) {
1070 if (callFrameId == 0) {
1071 return StackState::FAILED;
1072 }
1073 } else {
1074 SaveCallFrameHandler(frameHandler);
1075 callFrames->emplace_back(std::move(callFrame));
1076 callFrameId++;
1077 }
1078 return StackState::CONTINUE;
1079 };
1080 return DebuggerApi::StackWalker(vm_, walkerFunc);
1081 }
1082
SaveCallFrameHandler(const FrameHandler * frameHandler)1083 void DebuggerImpl::SaveCallFrameHandler(const FrameHandler *frameHandler)
1084 {
1085 auto handlerPtr = DebuggerApi::NewFrameHandler(vm_);
1086 *handlerPtr = *frameHandler;
1087 callFrameHandlers_.emplace_back(handlerPtr);
1088 }
1089
GenerateCallFrame(CallFrame * callFrame,const FrameHandler * frameHandler,CallFrameId callFrameId)1090 bool DebuggerImpl::GenerateCallFrame(CallFrame *callFrame,
1091 const FrameHandler *frameHandler, CallFrameId callFrameId)
1092 {
1093 if (!frameHandler->HasFrame()) {
1094 return false;
1095 }
1096 Method *method = DebuggerApi::GetMethod(frameHandler);
1097 auto methodId = method->GetMethodId();
1098 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1099 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1100 if (extractor == nullptr) {
1101 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: extractor is null";
1102 return false;
1103 }
1104
1105 // functionName
1106 std::string functionName = method->ParseFunctionName();
1107
1108 // location
1109 std::unique_ptr<Location> location = std::make_unique<Location>();
1110 std::string url = extractor->GetSourceFile(methodId);
1111 auto scriptFunc = [&location](PtScript *script) -> bool {
1112 location->SetScriptId(script->GetScriptId());
1113 return true;
1114 };
1115 if (!MatchScripts(scriptFunc, url, ScriptMatchType::URL)) {
1116 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: Unknown url: " << url;
1117 return false;
1118 }
1119 auto callbackLineFunc = [&location](int32_t line) -> bool {
1120 location->SetLine(line);
1121 return true;
1122 };
1123 auto callbackColumnFunc = [&location](int32_t column) -> bool {
1124 location->SetColumn(column);
1125 return true;
1126 };
1127 if (!extractor->MatchLineWithOffset(callbackLineFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler)) ||
1128 !extractor->MatchColumnWithOffset(callbackColumnFunc, methodId, DebuggerApi::GetBytecodeOffset(frameHandler))) {
1129 LOG_DEBUGGER(ERROR) << "GenerateCallFrame: unknown offset: " << DebuggerApi::GetBytecodeOffset(frameHandler);
1130 return false;
1131 }
1132
1133 // scopeChain & this
1134 std::unique_ptr<RemoteObject> thisObj = std::make_unique<RemoteObject>();
1135 thisObj->SetType(ObjectType::Undefined);
1136
1137 JSThread *thread = vm_->GetJSThread();
1138 std::vector<std::unique_ptr<Scope>> scopeChain;
1139 scopeChain.emplace_back(GetLocalScopeChain(frameHandler, &thisObj));
1140 if (jsPandaFile != nullptr && !jsPandaFile->IsBundlePack() && jsPandaFile->IsNewVersion()) {
1141 JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1142 if (currentModule->IsSourceTextModule()) { // CJS module is string
1143 scopeChain.emplace_back(GetModuleScopeChain());
1144 }
1145 }
1146 scopeChain.emplace_back(GetGlobalScopeChain());
1147
1148 callFrame->SetCallFrameId(callFrameId)
1149 .SetFunctionName(functionName)
1150 .SetLocation(std::move(location))
1151 .SetUrl(url)
1152 .SetScopeChain(std::move(scopeChain))
1153 .SetThis(std::move(thisObj));
1154 return true;
1155 }
1156
GetLocalScopeChain(const FrameHandler * frameHandler,std::unique_ptr<RemoteObject> * thisObj)1157 std::unique_ptr<Scope> DebuggerImpl::GetLocalScopeChain(const FrameHandler *frameHandler,
1158 std::unique_ptr<RemoteObject> *thisObj)
1159 {
1160 auto localScope = std::make_unique<Scope>();
1161
1162 Method *method = DebuggerApi::GetMethod(frameHandler);
1163 auto methodId = method->GetMethodId();
1164 const JSPandaFile *jsPandaFile = method->GetJSPandaFile();
1165 DebugInfoExtractor *extractor = GetExtractor(jsPandaFile);
1166 if (extractor == nullptr) {
1167 LOG_DEBUGGER(ERROR) << "GetScopeChain: extractor is null";
1168 return localScope;
1169 }
1170
1171 std::unique_ptr<RemoteObject> local = std::make_unique<RemoteObject>();
1172 Local<ObjectRef> localObj = ObjectRef::New(vm_);
1173 local->SetType(ObjectType::Object)
1174 .SetObjectId(runtime_->curObjectId_)
1175 .SetClassName(ObjectClassName::Object)
1176 .SetDescription(RemoteObject::ObjectDescription);
1177 auto *sp = DebuggerApi::GetSp(frameHandler);
1178 scopeObjects_[sp] = runtime_->curObjectId_;
1179 DebuggerApi::AddInternalProperties(vm_, localObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1180 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, localObj);
1181
1182 Local<JSValueRef> thisVal = JSNApiHelper::ToLocal<JSValueRef>(
1183 JSHandle<JSTaggedValue>(vm_->GetJSThread(), JSTaggedValue::Hole()));
1184 GetLocalVariables(frameHandler, methodId, jsPandaFile, thisVal, localObj);
1185 GetClosureVariables(frameHandler, thisVal, localObj);
1186 *thisObj = RemoteObject::FromTagged(vm_, thisVal);
1187 runtime_->CacheObjectIfNeeded(thisVal, (*thisObj).get());
1188
1189 const LineNumberTable &lines = extractor->GetLineNumberTable(methodId);
1190 std::unique_ptr<Location> startLoc = std::make_unique<Location>();
1191 std::unique_ptr<Location> endLoc = std::make_unique<Location>();
1192 auto scriptFunc = [&startLoc, &endLoc, lines](PtScript *script) -> bool {
1193 startLoc->SetScriptId(script->GetScriptId())
1194 .SetLine(lines.front().line)
1195 .SetColumn(0);
1196 endLoc->SetScriptId(script->GetScriptId())
1197 .SetLine(lines.back().line + 1)
1198 .SetColumn(0);
1199 return true;
1200 };
1201 if (MatchScripts(scriptFunc, extractor->GetSourceFile(methodId), ScriptMatchType::URL)) {
1202 localScope->SetType(Scope::Type::Local())
1203 .SetObject(std::move(local))
1204 .SetStartLocation(std::move(startLoc))
1205 .SetEndLocation(std::move(endLoc));
1206 }
1207
1208 return localScope;
1209 }
1210
GetModuleScopeChain()1211 std::unique_ptr<Scope> DebuggerImpl::GetModuleScopeChain()
1212 {
1213 auto moduleScope = std::make_unique<Scope>();
1214
1215 std::unique_ptr<RemoteObject> module = std::make_unique<RemoteObject>();
1216 Local<ObjectRef> moduleObj = ObjectRef::New(vm_);
1217 module->SetType(ObjectType::Object)
1218 .SetObjectId(runtime_->curObjectId_)
1219 .SetClassName(ObjectClassName::Object)
1220 .SetDescription(RemoteObject::ObjectDescription);
1221 moduleScope->SetType(Scope::Type::Module()).SetObject(std::move(module));
1222 DebuggerApi::AddInternalProperties(vm_, moduleObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1223 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, moduleObj);
1224 JSThread *thread = vm_->GetJSThread();
1225 JSHandle<JSTaggedValue> currentModule(thread, DebuggerApi::GetCurrentModule(vm_));
1226 DebuggerApi::GetLocalExportVariables(vm_, moduleObj, currentModule, false);
1227 DebuggerApi::GetIndirectExportVariables(vm_, moduleObj, currentModule);
1228 DebuggerApi::GetImportVariables(vm_, moduleObj, currentModule);
1229 return moduleScope;
1230 }
1231
GetLocalVariables(const FrameHandler * frameHandler,panda_file::File::EntityId methodId,const JSPandaFile * jsPandaFile,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1232 void DebuggerImpl::GetLocalVariables(const FrameHandler *frameHandler, panda_file::File::EntityId methodId,
1233 const JSPandaFile *jsPandaFile, Local<JSValueRef> &thisVal, Local<ObjectRef> &localObj)
1234 {
1235 auto *extractor = GetExtractor(jsPandaFile);
1236 Local<JSValueRef> value = JSValueRef::Undefined(vm_);
1237 // in case of arrow function, which doesn't have this in local variable table
1238 for (const auto &localVariableInfo : extractor->GetLocalVariableTable(methodId)) {
1239 std::string varName = localVariableInfo.name;
1240 int32_t regIndex = localVariableInfo.regNumber;
1241 uint32_t bcOffset = DebuggerApi::GetBytecodeOffset(frameHandler);
1242 // if the bytecodeOffset is not in the range of the variable's scope,
1243 // which is indicated as [start_offset, end_offset), ignore it.
1244 if (!IsWithinVariableScope(localVariableInfo, bcOffset)) {
1245 continue;
1246 }
1247
1248 if (varName == "4newTarget" || varName == "0this" || varName == "0newTarget" || varName == "0funcObj") {
1249 continue;
1250 }
1251
1252 value = DebuggerApi::GetVRegValue(vm_, frameHandler, regIndex);
1253 if (varName == "this") {
1254 LOG_DEBUGGER(INFO) << "find 'this' in local variable table";
1255 thisVal = value;
1256 continue;
1257 }
1258 Local<JSValueRef> name = JSValueRef::Undefined(vm_);
1259 if (varName == "4funcObj") {
1260 if (value->IsFunction()) {
1261 auto funcName = Local<FunctionRef>(value)->GetName(vm_)->ToString();
1262 name = StringRef::NewFromUtf8(vm_, funcName.c_str());
1263 } else {
1264 continue;
1265 }
1266 } else {
1267 name = StringRef::NewFromUtf8(vm_, varName.c_str());
1268 }
1269 PropertyAttribute descriptor(value, true, true, true);
1270 localObj->DefineProperty(vm_, name, descriptor);
1271 }
1272 }
1273
IsWithinVariableScope(const LocalVariableInfo & localVariableInfo,uint32_t bcOffset)1274 bool DebuggerImpl::IsWithinVariableScope(const LocalVariableInfo &localVariableInfo, uint32_t bcOffset)
1275 {
1276 return bcOffset >= localVariableInfo.startOffset && bcOffset < localVariableInfo.endOffset;
1277 }
1278
GetClosureVariables(const FrameHandler * frameHandler,Local<JSValueRef> & thisVal,Local<ObjectRef> & localObj)1279 void DebuggerImpl::GetClosureVariables(const FrameHandler *frameHandler, Local<JSValueRef> &thisVal,
1280 Local<ObjectRef> &localObj)
1281 {
1282 JSThread *thread = vm_->GetJSThread();
1283 JSHandle<JSTaggedValue> envHandle = JSHandle<JSTaggedValue>(thread, DebuggerApi::GetEnv(frameHandler));
1284 JSTaggedValue env = envHandle.GetTaggedValue();
1285 if (env.IsTaggedArray() && DebuggerApi::GetBytecodeOffset(frameHandler) != 0) {
1286 LexicalEnv *lexEnv = LexicalEnv::Cast(env.GetTaggedObject());
1287 if (lexEnv->GetScopeInfo().IsHole()) {
1288 return;
1289 }
1290 auto *scopeDebugInfo = reinterpret_cast<ScopeDebugInfo *>(JSNativePointer::Cast(
1291 lexEnv->GetScopeInfo().GetTaggedObject())->GetExternalPointer());
1292 for (const auto &[varName, slot] : scopeDebugInfo->scopeInfo) {
1293 // skip possible duplicate variables both in local variable table and env
1294 if (varName == "4newTarget") {
1295 continue;
1296 }
1297 env = envHandle.GetTaggedValue();
1298 lexEnv = LexicalEnv::Cast(env.GetTaggedObject());
1299 ASSERT(slot < lexEnv->GetLength() - LexicalEnv::RESERVED_ENV_LENGTH);
1300 Local<JSValueRef> value = JSNApiHelper::ToLocal<JSValueRef>(
1301 JSHandle<JSTaggedValue>(thread, lexEnv->GetProperties(slot)));
1302 if (varName == "this") {
1303 if (thisVal->IsHole()) {
1304 LOG_DEBUGGER(INFO) << "find 'this' in current lexical env";
1305 thisVal = value;
1306 }
1307 continue;
1308 }
1309 Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1310 if (value->IsHole()) {
1311 value = JSValueRef::Undefined(vm_);
1312 }
1313 PropertyAttribute descriptor(value, true, true, true);
1314 localObj->DefineProperty(vm_, name, descriptor);
1315 }
1316 }
1317
1318 // if 'this' is not in current lexical env, we should try to find from it's parent env
1319 if (thisVal->IsHole()) {
1320 auto [level, slot] = DebuggerApi::GetLevelSlot(frameHandler, "this");
1321 if (LIKELY(level != -1)) {
1322 LOG_DEBUGGER(INFO) << "find 'this' in parent lexical env";
1323 thisVal = DebuggerApi::GetProperties(vm_, frameHandler, level, slot);
1324 } else {
1325 thisVal = JSValueRef::Undefined(vm_);
1326 }
1327 }
1328 }
1329
GetGlobalScopeChain()1330 std::unique_ptr<Scope> DebuggerImpl::GetGlobalScopeChain()
1331 {
1332 auto globalScope = std::make_unique<Scope>();
1333
1334 std::unique_ptr<RemoteObject> global = std::make_unique<RemoteObject>();
1335 Local<ObjectRef> globalObj = ObjectRef::New(vm_);
1336 global->SetType(ObjectType::Object)
1337 .SetObjectId(runtime_->curObjectId_)
1338 .SetClassName(ObjectClassName::Global)
1339 .SetDescription(RemoteObject::GlobalDescription);
1340 globalScope->SetType(Scope::Type::Global()).SetObject(std::move(global));
1341 globalObj = JSNApi::GetGlobalObject(vm_);
1342 DebuggerApi::AddInternalProperties(vm_, globalObj, ArkInternalValueType::Scope, runtime_->internalObjects_);
1343 runtime_->properties_[runtime_->curObjectId_++] = Global<JSValueRef>(vm_, globalObj);
1344 return globalScope;
1345 }
1346
UpdateScopeObject(const FrameHandler * frameHandler,std::string_view varName,Local<JSValueRef> newVal)1347 void DebuggerImpl::UpdateScopeObject(const FrameHandler *frameHandler,
1348 std::string_view varName, Local<JSValueRef> newVal)
1349 {
1350 auto *sp = DebuggerApi::GetSp(frameHandler);
1351 auto iter = scopeObjects_.find(sp);
1352 if (iter == scopeObjects_.end()) {
1353 LOG_DEBUGGER(ERROR) << "UpdateScopeObject: object not found";
1354 return;
1355 }
1356
1357 auto objectId = iter->second;
1358 Local<ObjectRef> localObj = runtime_->properties_[objectId].ToLocal(vm_);
1359 Local<JSValueRef> name = StringRef::NewFromUtf8(vm_, varName.data());
1360 if (localObj->Has(vm_, name)) {
1361 LOG_DEBUGGER(DEBUG) << "UpdateScopeObject: set new value";
1362 PropertyAttribute descriptor(newVal, true, true, true);
1363 localObj->DefineProperty(vm_, name, descriptor);
1364 } else {
1365 LOG_DEBUGGER(ERROR) << "UpdateScopeObject: not found " << varName;
1366 }
1367 }
1368
ClearSingleStepper()1369 void DebuggerImpl::ClearSingleStepper()
1370 {
1371 // ClearSingleStepper is originally called from Function::Call, if current depth is 0, then it is safe to reset
1372 if (singleStepper_ != nullptr && DebuggerApi::GetStackDepth(vm_) == 0) {
1373 singleStepper_.reset();
1374 }
1375 }
1376
CmptEvaluateValue(CallFrameId callFrameId,const std::string & expression,std::unique_ptr<RemoteObject> * result)1377 std::optional<std::string> DebuggerImpl::CmptEvaluateValue(CallFrameId callFrameId, const std::string &expression,
1378 std::unique_ptr<RemoteObject> *result)
1379 {
1380 if (DebuggerApi::IsNativeMethod(vm_)) {
1381 *result = RemoteObject::FromTagged(vm_,
1382 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Native Frame not support.")));
1383 return "Native Frame not support.";
1384 }
1385 DebugInfoExtractor *extractor = GetExtractor(DebuggerApi::GetJSPandaFile(vm_));
1386 if (extractor == nullptr) {
1387 *result = RemoteObject::FromTagged(vm_,
1388 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Internal error.")));
1389 return "Internal error.";
1390 }
1391 std::string varName = expression;
1392 std::string varValue;
1393 std::string::size_type indexEqual = expression.find_first_of('=', 0);
1394 if (indexEqual != std::string::npos) {
1395 varName = Trim(expression.substr(0, indexEqual));
1396 varValue = Trim(expression.substr(indexEqual + 1, expression.length()));
1397 }
1398
1399 Local<StringRef> name = StringRef::NewFromUtf8(vm_, varName.c_str());
1400 FrameHandler *frameHandler = callFrameHandlers_[callFrameId].get();
1401 if (varValue.empty()) {
1402 Local<JSValueRef> ret = DebuggerExecutor::GetValue(vm_, frameHandler, name);
1403 if (!ret.IsEmpty()) {
1404 *result = RemoteObject::FromTagged(vm_, ret);
1405 runtime_->CacheObjectIfNeeded(ret, (*result).get());
1406 return {};
1407 }
1408 } else {
1409 Local<JSValueRef> value = ConvertToLocal(varValue);
1410 if (value.IsEmpty()) {
1411 return "Unsupported expression.";
1412 }
1413 JsDebuggerManager *mgr = vm_->GetJsDebuggerManager();
1414 mgr->SetEvalFrameHandler(callFrameHandlers_[callFrameId]);
1415 bool ret = DebuggerExecutor::SetValue(vm_, frameHandler, name, value);
1416 mgr->SetEvalFrameHandler(nullptr);
1417 if (ret) {
1418 *result = RemoteObject::FromTagged(vm_, value);
1419 return {};
1420 }
1421 }
1422
1423 *result = RemoteObject::FromTagged(vm_,
1424 Exception::EvalError(vm_, StringRef::NewFromUtf8(vm_, "Unsupported expression.")));
1425 return "Unsupported expression.";
1426 }
1427
ConvertToLocal(const std::string & varValue)1428 Local<JSValueRef> DebuggerImpl::ConvertToLocal(const std::string &varValue)
1429 {
1430 Local<JSValueRef> taggedValue;
1431 if (varValue == "false") {
1432 taggedValue = JSValueRef::False(vm_);
1433 } else if (varValue == "true") {
1434 taggedValue = JSValueRef::True(vm_);
1435 } else if (varValue == "undefined") {
1436 taggedValue = JSValueRef::Undefined(vm_);
1437 } else if (varValue[0] == '\"' && varValue[varValue.length() - 1] == '\"') {
1438 // 2 : 2 means length
1439 taggedValue = StringRef::NewFromUtf8(vm_, varValue.substr(1, varValue.length() - 2).c_str());
1440 } else {
1441 auto begin = reinterpret_cast<const uint8_t *>((varValue.c_str()));
1442 auto end = begin + varValue.length(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1443 double d = DebuggerApi::StringToDouble(begin, end, 0);
1444 if (!std::isnan(d)) {
1445 taggedValue = NumberRef::New(vm_, d);
1446 }
1447 }
1448 return taggedValue;
1449 }
1450
DecodeAndCheckBase64(const std::string & src,std::vector<uint8_t> & dest)1451 bool DebuggerImpl::DecodeAndCheckBase64(const std::string &src, std::vector<uint8_t> &dest)
1452 {
1453 dest.resize(PtBase64::DecodedSize(src.size()));
1454 auto [numOctets, done] = PtBase64::Decode(dest.data(), src.data(), src.size());
1455 dest.resize(numOctets);
1456 if ((done && numOctets > panda_file::File::MAGIC_SIZE) &&
1457 memcmp(dest.data(), panda_file::File::MAGIC.data(), panda_file::File::MAGIC_SIZE) == 0) {
1458 return true;
1459 }
1460 return false;
1461 }
1462 } // namespace panda::ecmascript::tooling
1463