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 "ecmascript/debugger/js_debugger.h"
17
18 #include "ecmascript/interpreter/interpreter-inl.h"
19 #include "ecmascript/js_thread.h"
20
21 namespace panda::ecmascript::tooling {
22 using panda::ecmascript::base::BuiltinsBase;
23
SetBreakpoint(const JSPtLocation & location,Local<FunctionRef> condFuncRef)24 bool JSDebugger::SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef)
25 {
26 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
27 if (ptMethod == nullptr) {
28 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Cannot find MethodLiteral";
29 return false;
30 }
31
32 if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
33 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Invalid breakpoint location";
34 return false;
35 }
36
37 auto [_, success] = breakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
38 location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, condFuncRef));
39 if (!success) {
40 // also return true
41 LOG_DEBUGGER(WARN) << "SetBreakpoint: Breakpoint already exists";
42 }
43
44 DumpBreakpoints();
45 return true;
46 }
47
SetSmartBreakpoint(const JSPtLocation & location)48 bool JSDebugger::SetSmartBreakpoint(const JSPtLocation &location)
49 {
50 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
51 if (ptMethod == nullptr) {
52 LOG_DEBUGGER(ERROR) << "SetSmartBreakpoint: Cannot find MethodLiteral";
53 return false;
54 }
55
56 if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
57 LOG_DEBUGGER(ERROR) << "SetSmartBreakpoint: Invalid breakpoint location";
58 return false;
59 }
60
61 auto [_, success] = smartBreakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
62 location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, FunctionRef::Undefined(ecmaVm_)));
63 if (!success) {
64 // also return true
65 LOG_DEBUGGER(WARN) << "SetSmartBreakpoint: Breakpoint already exists";
66 }
67
68 DumpBreakpoints();
69 return true;
70 }
71
RemoveBreakpoint(const JSPtLocation & location)72 bool JSDebugger::RemoveBreakpoint(const JSPtLocation &location)
73 {
74 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
75 if (ptMethod == nullptr) {
76 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Cannot find MethodLiteral";
77 return false;
78 }
79
80 if (!RemoveBreakpoint(ptMethod, location.GetBytecodeOffset())) {
81 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Breakpoint not found";
82 return false;
83 }
84
85 DumpBreakpoints();
86 return true;
87 }
88
RemoveAllBreakpoints()89 void JSDebugger::RemoveAllBreakpoints()
90 {
91 breakpoints_.clear();
92 }
93
RemoveBreakpointsByUrl(const std::string & url)94 bool JSDebugger::RemoveBreakpointsByUrl(const std::string &url)
95 {
96 for (auto it = breakpoints_.begin(); it != breakpoints_.end();) {
97 const auto &bp = *it;
98 if (bp.GetSourceFile() == url) {
99 it = breakpoints_.erase(it);
100 } else {
101 it++;
102 }
103 }
104
105 DumpBreakpoints();
106 return true;
107 }
108
BytecodePcChanged(JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)109 void JSDebugger::BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
110 {
111 ASSERT(bcOffset < method->GetCodeSize(thread) && "code size of current Method less then bcOffset");
112 HandleExceptionThrowEvent(thread, method, bcOffset);
113 // clear singlestep flag
114 singleStepOnDebuggerStmt_ = false;
115 if (ecmaVm_->GetJsDebuggerManager()->IsMixedStackEnabled()) {
116 if (!HandleBreakpoint(method, bcOffset)) {
117 HandleNativeOut();
118 HandleStep(method, bcOffset);
119 }
120 } else {
121 if (!HandleStep(method, bcOffset)) {
122 HandleBreakpoint(method, bcOffset);
123 }
124 }
125 }
126
HandleNativeOut()127 bool JSDebugger::HandleNativeOut()
128 {
129 if (hooks_ == nullptr) {
130 return false;
131 }
132
133 return hooks_->NativeOut();
134 }
135
HandleBreakpoint(JSHandle<Method> method,uint32_t bcOffset)136 bool JSDebugger::HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset)
137 {
138 if (hooks_ == nullptr) {
139 return false;
140 }
141
142 JSThread *thread = ecmaVm_->GetJSThread();
143 auto smartBreakpoint = FindSmartBreakpoint(method, bcOffset);
144 if (smartBreakpoint.has_value()) {
145 JSPtLocation smartLocation {method->GetJSPandaFile(thread), method->GetMethodId(), bcOffset,
146 smartBreakpoint.value().GetSourceFile()};
147 std::unique_ptr<PtMethod> ptMethod = FindMethod(smartLocation);
148 RemoveSmartBreakpoint(ptMethod, bcOffset);
149 hooks_->Breakpoint(smartLocation);
150 return true;
151 }
152
153 auto breakpoint = FindBreakpoint(method, bcOffset);
154 if (!breakpoint.has_value() || !IsBreakpointCondSatisfied(breakpoint)) {
155 return false;
156 }
157 JSPtLocation location {method->GetJSPandaFile(thread), method->GetMethodId(), bcOffset,
158 breakpoint.value().GetSourceFile()};
159
160 hooks_->Breakpoint(location);
161 return true;
162 }
163
HandleDebuggerStmt(JSHandle<Method> method,uint32_t bcOffset)164 bool JSDebugger::HandleDebuggerStmt(JSHandle<Method> method, uint32_t bcOffset)
165 {
166 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
167 return false;
168 }
169 // if debugger stmt is met by single stepping, disable debugger
170 // stmt to prevent pausing on this line twice
171 if (singleStepOnDebuggerStmt_) {
172 return false;
173 }
174 auto breakpointAtDebugger = FindBreakpoint(method, bcOffset);
175 // if a breakpoint is set on the same line as debugger stmt,
176 // the debugger stmt is ineffective
177 if (breakpointAtDebugger.has_value()) {
178 return false;
179 }
180 JSThread *thread = ecmaVm_->GetJSThread();
181 JSPtLocation location {method->GetJSPandaFile(thread), method->GetMethodId(), bcOffset};
182 hooks_->DebuggerStmt(location);
183
184 return true;
185 }
186
HandleExceptionThrowEvent(const JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)187 void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
188 {
189 if (hooks_ == nullptr || !thread->HasPendingException()) {
190 return;
191 }
192
193 JSPtLocation throwLocation {method->GetJSPandaFile(thread), method->GetMethodId(), bcOffset};
194
195 hooks_->Exception(throwLocation);
196 }
197
HandleStep(JSHandle<Method> method,uint32_t bcOffset)198 bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
199 {
200 if (hooks_ == nullptr) {
201 return false;
202 }
203
204 JSThread *thread = ecmaVm_->GetJSThread();
205 JSPtLocation location {method->GetJSPandaFile(thread), method->GetMethodId(), bcOffset};
206
207 return hooks_->SingleStep(location);
208 }
209
FindBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const210 std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
211 {
212 JSThread *thread = ecmaVm_->GetJSThread();
213 for (const auto &bp : breakpoints_) {
214 if ((bp.GetBytecodeOffset() == bcOffset) &&
215 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile(thread)) &&
216 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
217 return bp;
218 }
219 }
220 return {};
221 }
222
FindSmartBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const223 std::optional<JSBreakpoint> JSDebugger::FindSmartBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
224 {
225 JSThread *thread = ecmaVm_->GetJSThread();
226 for (const auto &bp : smartBreakpoints_) {
227 if ((bp.GetBytecodeOffset() == bcOffset) &&
228 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile(thread)) &&
229 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
230 return bp;
231 }
232 }
233 return {};
234 }
235
RemoveBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)236 bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
237 {
238 for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
239 const auto &bp = *it;
240 if ((bp.GetBytecodeOffset() == bcOffset) &&
241 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
242 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
243 it = breakpoints_.erase(it);
244 return true;
245 }
246 }
247
248 return false;
249 }
250
RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)251 bool JSDebugger::RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
252 {
253 for (auto it = smartBreakpoints_.begin(); it != smartBreakpoints_.end(); ++it) {
254 const auto &bp = *it;
255 if ((bp.GetBytecodeOffset() == bcOffset) &&
256 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
257 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
258 it = smartBreakpoints_.erase(it);
259 return true;
260 }
261 }
262
263 return false;
264 }
265
FindMethod(const JSPtLocation & location) const266 std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
267 {
268 std::unique_ptr<PtMethod> ptMethod {nullptr};
269 ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
270 const std::shared_ptr<JSPandaFile> &file) {
271 if (file->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
272 MethodLiteral *methodsData = file->GetMethodLiterals();
273 uint32_t numberMethods = file->GetNumMethods();
274 for (uint32_t i = 0; i < numberMethods; ++i) {
275 if (methodsData[i].GetMethodId() == location.GetMethodId()) {
276 MethodLiteral *methodLiteral = methodsData + i;
277 ptMethod = std::make_unique<PtMethod>(file.get(),
278 methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
279 return false;
280 }
281 }
282 }
283 return true;
284 });
285 return ptMethod;
286 }
287
DumpBreakpoints()288 void JSDebugger::DumpBreakpoints()
289 {
290 LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
291 for (const auto &bp : breakpoints_) {
292 LOG_DEBUGGER(DEBUG) << bp.ToString();
293 }
294 }
295
IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const296 bool JSDebugger::IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const
297 {
298 if (!breakpoint.has_value()) {
299 return false;
300 }
301 JSThread *thread = ecmaVm_->GetJSThread();
302 auto condFuncRef = breakpoint.value().GetConditionFunction();
303 if (condFuncRef->IsFunction(ecmaVm_)) {
304 LOG_DEBUGGER(INFO) << "BreakpointCondition: evaluating condition";
305 auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
306 auto evalResult = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
307 condFuncRef.ToLocal(ecmaVm_), handlerPtr);
308 if (thread->HasPendingException()) {
309 LOG_DEBUGGER(ERROR) << "BreakpointCondition: has pending exception";
310 thread->ClearException();
311 return false;
312 }
313 bool satisfied = evalResult->ToBoolean(ecmaVm_)->Value();
314 if (!satisfied) {
315 LOG_DEBUGGER(INFO) << "BreakpointCondition: condition not meet";
316 return false;
317 }
318 }
319 return true;
320 }
321
HandleSymbolicBreakpoint(const JSHandle<Method> & method)322 void JSDebugger::HandleSymbolicBreakpoint(const JSHandle<Method> &method)
323 {
324 if (symbolicBreakpoints_.empty()) {
325 return;
326 }
327 std::unordered_set<std::string> recordNames = hooks_->GetAllRecordNames();
328 JSThread *thread = ecmaVm_->GetJSThread();
329 std::string recordName(method->GetRecordNameStr(thread).c_str());
330 if (recordNames.find(recordName) == recordNames.end()) {
331 return;
332 }
333 auto symbolicBreakpoint = symbolicBreakpoints_.find(method->ParseFunctionName(thread));
334 if (symbolicBreakpoint != symbolicBreakpoints_.end()) {
335 hooks_->HitSymbolicBreakpoint();
336 }
337 }
338
MethodEntry(JSHandle<Method> method,JSHandle<JSTaggedValue> envHandle)339 void JSDebugger::MethodEntry(JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle)
340 {
341 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
342 return;
343 }
344 FrameHandler frameHandler(ecmaVm_->GetJSThread());
345 // No frame before the first method is executed
346 if (!frameHandler.HasFrame()) {
347 // Handle first method
348 HandleSymbolicBreakpoint(method);
349 return;
350 }
351 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
352 return;
353 }
354 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
355 debuggerMgr->MethodEntry(method, envHandle);
356
357 // scriptParsed for sendable object
358 if (method->IsSendableMethod()) {
359 hooks_->SendableMethodEntry(method);
360 }
361
362 // pause for symbolicBreakpoints
363 HandleSymbolicBreakpoint(method);
364 }
365
MethodExit(JSHandle<Method> method)366 void JSDebugger::MethodExit([[maybe_unused]] JSHandle<Method> method)
367 {
368 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
369 return;
370 }
371 FrameHandler frameHandler(ecmaVm_->GetJSThread());
372 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
373 return;
374 }
375 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
376 debuggerMgr->MethodExit(method);
377 }
378 } // namespace panda::tooling::ecmascript
379