• 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/base/builtins_base.h"
20 #include "ecmascript/ecma_macros.h"
21 #include "ecmascript/global_env.h"
22 #include "ecmascript/interpreter/fast_runtime_stub-inl.h"
23 #include "ecmascript/interpreter/frame_handler.h"
24 #include "ecmascript/jspandafile/js_pandafile_manager.h"
25 #include "ecmascript/interpreter/interpreter-inl.h"
26 
27 namespace panda::ecmascript::tooling {
28 using panda::ecmascript::base::BuiltinsBase;
29 
SetBreakpoint(const JSPtLocation & location,Local<FunctionRef> condFuncRef)30 bool JSDebugger::SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef)
31 {
32     std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
33     if (ptMethod == nullptr) {
34         LOG_DEBUGGER(ERROR) << "SetBreakpoint: Cannot find MethodLiteral";
35         return false;
36     }
37 
38     if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
39         LOG_DEBUGGER(ERROR) << "SetBreakpoint: Invalid breakpoint location";
40         return false;
41     }
42 
43     auto [_, success] = breakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
44         location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, condFuncRef));
45     if (!success) {
46         // also return true
47         LOG_DEBUGGER(WARN) << "SetBreakpoint: Breakpoint already exists";
48     }
49 
50     DumpBreakpoints();
51     return true;
52 }
53 
RemoveBreakpoint(const JSPtLocation & location)54 bool JSDebugger::RemoveBreakpoint(const JSPtLocation &location)
55 {
56     std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
57     if (ptMethod == nullptr) {
58         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Cannot find MethodLiteral";
59         return false;
60     }
61 
62     if (!RemoveBreakpoint(ptMethod, location.GetBytecodeOffset())) {
63         LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Breakpoint not found";
64         return false;
65     }
66 
67     DumpBreakpoints();
68     return true;
69 }
70 
RemoveAllBreakpoints()71 void JSDebugger::RemoveAllBreakpoints()
72 {
73     breakpoints_.clear();
74 }
75 
BytecodePcChanged(JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)76 void JSDebugger::BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
77 {
78     ASSERT(bcOffset < method->GetCodeSize() && "code size of current Method less then bcOffset");
79     HandleExceptionThrowEvent(thread, method, bcOffset);
80     // clear singlestep flag
81     singleStepOnDebuggerStmt_ = false;
82     if (ecmaVm_->GetJsDebuggerManager()->IsMixedStackEnabled()) {
83         if (!HandleBreakpoint(method, bcOffset)) {
84             HandleNativeOut();
85             HandleStep(method, bcOffset);
86         }
87     } else  {
88         if (!HandleStep(method, bcOffset)) {
89             HandleBreakpoint(method, bcOffset);
90         }
91     }
92 }
93 
HandleNativeOut()94 bool JSDebugger::HandleNativeOut()
95 {
96     if (hooks_ == nullptr) {
97         return false;
98     }
99 
100     return hooks_->NativeOut();
101 }
102 
HandleBreakpoint(JSHandle<Method> method,uint32_t bcOffset)103 bool JSDebugger::HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset)
104 {
105     auto breakpoint = FindBreakpoint(method, bcOffset);
106     if (hooks_ == nullptr || !breakpoint.has_value()) {
107         return false;
108     }
109     if (!IsBreakpointCondSatisfied(breakpoint)) {
110         return false;
111     }
112     JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
113         breakpoint.value().GetSourceFile()};
114 
115     hooks_->Breakpoint(location);
116     return true;
117 }
118 
HandleDebuggerStmt(JSHandle<Method> method,uint32_t bcOffset)119 bool JSDebugger::HandleDebuggerStmt(JSHandle<Method> method, uint32_t bcOffset)
120 {
121     if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
122         return false;
123     }
124     // if debugger stmt is met by single stepping, disable debugger
125     // stmt to prevent pausing on this line twice
126     if (singleStepOnDebuggerStmt_) {
127         return false;
128     }
129     auto breakpointAtDebugger = FindBreakpoint(method, bcOffset);
130     // if a breakpoint is set on the same line as debugger stmt,
131     // the debugger stmt is ineffective
132     if (breakpointAtDebugger.has_value()) {
133         return false;
134     }
135     JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
136     hooks_->DebuggerStmt(location);
137 
138     return true;
139 }
140 
HandleExceptionThrowEvent(const JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)141 void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
142 {
143     if (hooks_ == nullptr || !thread->HasPendingException()) {
144         return;
145     }
146 
147     JSPtLocation throwLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
148 
149     hooks_->Exception(throwLocation);
150 }
151 
HandleStep(JSHandle<Method> method,uint32_t bcOffset)152 bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
153 {
154     if (hooks_ == nullptr) {
155         return false;
156     }
157 
158     JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
159 
160     return hooks_->SingleStep(location);
161 }
162 
FindBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const163 std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
164 {
165     for (const auto &bp : breakpoints_) {
166         if ((bp.GetBytecodeOffset() == bcOffset) &&
167             (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
168             (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
169             return bp;
170         }
171     }
172     return {};
173 }
174 
RemoveBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)175 bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
176 {
177     for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
178         const auto &bp = *it;
179         if ((bp.GetBytecodeOffset() == bcOffset) &&
180             (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
181             (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
182             it = breakpoints_.erase(it);
183             return true;
184         }
185     }
186 
187     return false;
188 }
189 
FindMethod(const JSPtLocation & location) const190 std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
191 {
192     std::unique_ptr<PtMethod> ptMethod {nullptr};
193     ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
194         const std::shared_ptr<JSPandaFile> &file) {
195         if (file->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
196             MethodLiteral *methodsData = file->GetMethodLiterals();
197             uint32_t numberMethods = file->GetNumMethods();
198             for (uint32_t i = 0; i < numberMethods; ++i) {
199                 if (methodsData[i].GetMethodId() == location.GetMethodId()) {
200                     MethodLiteral *methodLiteral = methodsData + i;
201                     ptMethod = std::make_unique<PtMethod>(file.get(),
202                         methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
203                     return false;
204                 }
205             }
206         }
207         return true;
208     });
209     return ptMethod;
210 }
211 
DumpBreakpoints()212 void JSDebugger::DumpBreakpoints()
213 {
214     LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
215     for (const auto &bp : breakpoints_) {
216         LOG_DEBUGGER(DEBUG) << bp.ToString();
217     }
218 }
219 
IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const220 bool JSDebugger::IsBreakpointCondSatisfied(std::optional<JSBreakpoint> breakpoint) const
221 {
222     if (!breakpoint.has_value()) {
223         return false;
224     }
225     JSThread *thread = ecmaVm_->GetJSThread();
226     auto condFuncRef = breakpoint.value().GetConditionFunction();
227     if (condFuncRef->IsFunction()) {
228         LOG_DEBUGGER(INFO) << "BreakpointCondition: evaluating condition";
229         auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
230         auto evalResult = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
231             condFuncRef.ToLocal(ecmaVm_), handlerPtr);
232         if (thread->HasPendingException()) {
233             LOG_DEBUGGER(ERROR) << "BreakpointCondition: has pending exception";
234             thread->ClearException();
235             return false;
236         }
237         bool satisfied = evalResult->ToBoolean(ecmaVm_)->Value();
238         if (!satisfied) {
239             LOG_DEBUGGER(INFO) << "BreakpointCondition: condition not meet";
240             return false;
241         }
242     }
243     return true;
244 }
245 
MethodEntry(JSHandle<Method> method,JSHandle<JSTaggedValue> envHandle)246 void JSDebugger::MethodEntry(JSHandle<Method> method, JSHandle<JSTaggedValue> envHandle)
247 {
248     if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
249         return;
250     }
251     FrameHandler frameHandler(ecmaVm_->GetJSThread());
252     if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
253         return;
254     }
255     auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
256     debuggerMgr->MethodEntry(method, envHandle);
257 }
258 
MethodExit(JSHandle<Method> method)259 void JSDebugger::MethodExit([[maybe_unused]] JSHandle<Method> method)
260 {
261     if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
262         return;
263     }
264     FrameHandler frameHandler(ecmaVm_->GetJSThread());
265     if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
266         return;
267     }
268     auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
269     debuggerMgr->MethodExit(method);
270 }
271 }  // namespace panda::tooling::ecmascript
272