• 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 #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