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 #include <memory>
18
19 #include "ecmascript/interpreter/interpreter-inl.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() && "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 auto smartBreakpoint = FindSmartBreakpoint(method, bcOffset);
143 if (smartBreakpoint.has_value()) {
144 JSPtLocation smartLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
145 smartBreakpoint.value().GetSourceFile()};
146 std::unique_ptr<PtMethod> ptMethod = FindMethod(smartLocation);
147 RemoveSmartBreakpoint(ptMethod, bcOffset);
148 hooks_->Breakpoint(smartLocation);
149 return true;
150 }
151
152 auto breakpoint = FindBreakpoint(method, bcOffset);
153 if (!breakpoint.has_value() || !IsBreakpointCondSatisfied(breakpoint)) {
154 return false;
155 }
156 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
157 breakpoint.value().GetSourceFile()};
158
159 hooks_->Breakpoint(location);
160 return true;
161 }
162
HandleDebuggerStmt(JSHandle<Method> method,uint32_t bcOffset)163 bool JSDebugger::HandleDebuggerStmt(JSHandle<Method> method, uint32_t bcOffset)
164 {
165 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
166 return false;
167 }
168 // if debugger stmt is met by single stepping, disable debugger
169 // stmt to prevent pausing on this line twice
170 if (singleStepOnDebuggerStmt_) {
171 return false;
172 }
173 auto breakpointAtDebugger = FindBreakpoint(method, bcOffset);
174 // if a breakpoint is set on the same line as debugger stmt,
175 // the debugger stmt is ineffective
176 if (breakpointAtDebugger.has_value()) {
177 return false;
178 }
179 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
180 hooks_->DebuggerStmt(location);
181
182 return true;
183 }
184
HandleExceptionThrowEvent(const JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)185 void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
186 {
187 if (hooks_ == nullptr || !thread->HasPendingException()) {
188 return;
189 }
190
191 JSPtLocation throwLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
192
193 hooks_->Exception(throwLocation);
194 }
195
HandleStep(JSHandle<Method> method,uint32_t bcOffset)196 bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
197 {
198 if (hooks_ == nullptr) {
199 return false;
200 }
201
202 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
203
204 return hooks_->SingleStep(location);
205 }
206
FindBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const207 std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
208 {
209 for (const auto &bp : breakpoints_) {
210 if ((bp.GetBytecodeOffset() == bcOffset) &&
211 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
212 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
213 return bp;
214 }
215 }
216 return {};
217 }
218
FindSmartBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const219 std::optional<JSBreakpoint> JSDebugger::FindSmartBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
220 {
221 for (const auto &bp : smartBreakpoints_) {
222 if ((bp.GetBytecodeOffset() == bcOffset) &&
223 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
224 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
225 return bp;
226 }
227 }
228 return {};
229 }
230
RemoveBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)231 bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
232 {
233 for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
234 const auto &bp = *it;
235 if ((bp.GetBytecodeOffset() == bcOffset) &&
236 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
237 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
238 it = breakpoints_.erase(it);
239 return true;
240 }
241 }
242
243 return false;
244 }
245
RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)246 bool JSDebugger::RemoveSmartBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
247 {
248 for (auto it = smartBreakpoints_.begin(); it != smartBreakpoints_.end(); ++it) {
249 const auto &bp = *it;
250 if ((bp.GetBytecodeOffset() == bcOffset) &&
251 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
252 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
253 it = smartBreakpoints_.erase(it);
254 return true;
255 }
256 }
257
258 return false;
259 }
260
FindMethod(const JSPtLocation & location) const261 std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
262 {
263 std::unique_ptr<PtMethod> ptMethod {nullptr};
264 ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
265 const std::shared_ptr<JSPandaFile> &file) {
266 if (file->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
267 MethodLiteral *methodsData = file->GetMethodLiterals();
268 uint32_t numberMethods = file->GetNumMethods();
269 for (uint32_t i = 0; i < numberMethods; ++i) {
270 if (methodsData[i].GetMethodId() == location.GetMethodId()) {
271 MethodLiteral *methodLiteral = methodsData + i;
272 ptMethod = std::make_unique<PtMethod>(file.get(),
273 methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
274 return false;
275 }
276 }
277 }
278 return true;
279 });
280 return ptMethod;
281 }
282
DumpBreakpoints()283 void JSDebugger::DumpBreakpoints()
284 {
285 LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
286 for (const auto &bp : breakpoints_) {
287 LOG_DEBUGGER(DEBUG) << bp.ToString();
288 }
289 }
290
IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const291 bool JSDebugger::IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const
292 {
293 if (!breakpoint.has_value()) {
294 return false;
295 }
296 JSThread *thread = ecmaVm_->GetJSThread();
297 auto condFuncRef = breakpoint.value().GetConditionFunction();
298 if (condFuncRef->IsFunction(ecmaVm_)) {
299 LOG_DEBUGGER(INFO) << "BreakpointCondition: evaluating condition";
300 auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
301 auto evalResult = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
302 condFuncRef.ToLocal(ecmaVm_), handlerPtr);
303 if (thread->HasPendingException()) {
304 LOG_DEBUGGER(ERROR) << "BreakpointCondition: has pending exception";
305 thread->ClearException();
306 return false;
307 }
308 bool satisfied = evalResult->ToBoolean(ecmaVm_)->Value();
309 if (!satisfied) {
310 LOG_DEBUGGER(INFO) << "BreakpointCondition: condition not meet";
311 return false;
312 }
313 }
314 return true;
315 }
316
MethodEntry(JSHandle<Method> method,JSHandle<JSTaggedValue> envHandle)317 void JSDebugger::MethodEntry(JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle)
318 {
319 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
320 return;
321 }
322 FrameHandler frameHandler(ecmaVm_->GetJSThread());
323 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
324 return;
325 }
326 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
327 debuggerMgr->MethodEntry(method, envHandle);
328
329 // scriptParsed for sendable object
330 if (method->IsSendableMethod()) {
331 hooks_->SendableMethodEntry(method);
332 }
333 }
334
MethodExit(JSHandle<Method> method)335 void JSDebugger::MethodExit([[maybe_unused]] JSHandle<Method> method)
336 {
337 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
338 return;
339 }
340 FrameHandler frameHandler(ecmaVm_->GetJSThread());
341 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
342 return;
343 }
344 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
345 debuggerMgr->MethodExit(method);
346 }
347 } // namespace panda::tooling::ecmascript