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/base/builtins_base.h"
19 #include "ecmascript/ecma_macros.h"
20 #include "ecmascript/global_env.h"
21 #include "ecmascript/interpreter/fast_runtime_stub-inl.h"
22 #include "ecmascript/interpreter/frame_handler.h"
23 #include "ecmascript/jspandafile/js_pandafile_manager.h"
24 #include "ecmascript/interpreter/interpreter-inl.h"
25
26 namespace panda::ecmascript::tooling {
27 using panda::ecmascript::base::BuiltinsBase;
28
SetBreakpoint(const JSPtLocation & location,Local<FunctionRef> condFuncRef)29 bool JSDebugger::SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef)
30 {
31 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
32 if (ptMethod == nullptr) {
33 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Cannot find MethodLiteral";
34 return false;
35 }
36
37 if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
38 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Invalid breakpoint location";
39 return false;
40 }
41
42 auto [_, success] = breakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
43 location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, condFuncRef));
44 if (!success) {
45 // also return true
46 LOG_DEBUGGER(WARN) << "SetBreakpoint: Breakpoint already exists";
47 }
48
49 DumpBreakpoints();
50 return true;
51 }
52
RemoveBreakpoint(const JSPtLocation & location)53 bool JSDebugger::RemoveBreakpoint(const JSPtLocation &location)
54 {
55 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
56 if (ptMethod == nullptr) {
57 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Cannot find MethodLiteral";
58 return false;
59 }
60
61 if (!RemoveBreakpoint(ptMethod, location.GetBytecodeOffset())) {
62 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Breakpoint not found";
63 return false;
64 }
65
66 DumpBreakpoints();
67 return true;
68 }
69
RemoveAllBreakpoints()70 void JSDebugger::RemoveAllBreakpoints()
71 {
72 breakpoints_.clear();
73 }
74
BytecodePcChanged(JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)75 void JSDebugger::BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
76 {
77 ASSERT(bcOffset < method->GetCodeSize() && "code size of current Method less then bcOffset");
78 HandleExceptionThrowEvent(thread, method, bcOffset);
79
80 // Step event is reported before breakpoint, according to the spec.
81 if (!HandleStep(method, bcOffset)) {
82 HandleBreakpoint(method, bcOffset);
83 }
84 }
85
HandleBreakpoint(JSHandle<Method> method,uint32_t bcOffset)86 bool JSDebugger::HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset)
87 {
88 auto breakpoint = FindBreakpoint(method, bcOffset);
89 if (hooks_ == nullptr || !breakpoint.has_value()) {
90 return false;
91 }
92
93 JSThread *thread = ecmaVm_->GetJSThread();
94 auto condFuncRef = breakpoint.value().GetConditionFunction();
95 if (condFuncRef->IsFunction()) {
96 LOG_DEBUGGER(INFO) << "HandleBreakpoint: begin evaluate condition";
97 auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
98 auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
99 condFuncRef.ToLocal(ecmaVm_), handlerPtr);
100 if (thread->HasPendingException()) {
101 LOG_DEBUGGER(ERROR) << "HandleBreakpoint: has pending exception";
102 thread->ClearException();
103 return false;
104 }
105 bool isMeet = res->ToBoolean(ecmaVm_)->Value();
106 if (!isMeet) {
107 LOG_DEBUGGER(ERROR) << "HandleBreakpoint: condition not meet";
108 return false;
109 }
110 }
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
125 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
126 hooks_->DebuggerStmt(location);
127
128 return true;
129 }
130
HandleExceptionThrowEvent(const JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)131 void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
132 {
133 if (hooks_ == nullptr || !thread->HasPendingException()) {
134 return;
135 }
136
137 JSPtLocation throwLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
138
139 hooks_->Exception(throwLocation);
140 }
141
HandleStep(JSHandle<Method> method,uint32_t bcOffset)142 bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
143 {
144 if (hooks_ == nullptr) {
145 return false;
146 }
147
148 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
149
150 return hooks_->SingleStep(location);
151 }
152
FindBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const153 std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
154 {
155 for (const auto &bp : breakpoints_) {
156 if ((bp.GetBytecodeOffset() == bcOffset) &&
157 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
158 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
159 return bp;
160 }
161 }
162 return {};
163 }
164
RemoveBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)165 bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
166 {
167 for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
168 const auto &bp = *it;
169 if ((bp.GetBytecodeOffset() == bcOffset) &&
170 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
171 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
172 it = breakpoints_.erase(it);
173 return true;
174 }
175 }
176
177 return false;
178 }
179
FindMethod(const JSPtLocation & location) const180 std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
181 {
182 std::unique_ptr<PtMethod> ptMethod {nullptr};
183 ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
184 const JSPandaFile *jsPandaFile) {
185 if (jsPandaFile->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
186 MethodLiteral *methodsData = jsPandaFile->GetMethodLiterals();
187 uint32_t numberMethods = jsPandaFile->GetNumMethods();
188 for (uint32_t i = 0; i < numberMethods; ++i) {
189 if (methodsData[i].GetMethodId() == location.GetMethodId()) {
190 MethodLiteral *methodLiteral = methodsData + i;
191 ptMethod = std::make_unique<PtMethod>(jsPandaFile,
192 methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
193 return false;
194 }
195 }
196 }
197 return true;
198 });
199 return ptMethod;
200 }
201
DumpBreakpoints()202 void JSDebugger::DumpBreakpoints()
203 {
204 LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
205 for (const auto &bp : breakpoints_) {
206 LOG_DEBUGGER(DEBUG) << bp.ToString();
207 }
208 }
209
MethodEntry(JSHandle<Method> method)210 void JSDebugger::MethodEntry(JSHandle<Method> method)
211 {
212 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
213 return;
214 }
215 FrameHandler frameHandler(ecmaVm_->GetJSThread());
216 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame() || frameHandler.GetEnv().IsUndefinedOrNull()) {
217 return;
218 }
219 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
220 debuggerMgr->MethodEntry(method);
221 }
222
MethodExit(JSHandle<Method> method)223 void JSDebugger::MethodExit([[maybe_unused]] JSHandle<Method> method)
224 {
225 if (hooks_ == nullptr || !ecmaVm_->GetJsDebuggerManager()->IsDebugMode()) {
226 return;
227 }
228 FrameHandler frameHandler(ecmaVm_->GetJSThread());
229 if (frameHandler.IsEntryFrame() || frameHandler.IsBuiltinFrame()) {
230 return;
231 }
232 auto *debuggerMgr = ecmaVm_->GetJsDebuggerManager();
233 debuggerMgr->MethodExit(method);
234 }
235 } // namespace panda::tooling::ecmascript
236