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