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