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