• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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