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
25 namespace panda::ecmascript::tooling {
26 using panda::ecmascript::base::BuiltinsBase;
27
SetBreakpoint(const JSPtLocation & location,Local<FunctionRef> condFuncRef)28 bool JSDebugger::SetBreakpoint(const JSPtLocation &location, Local<FunctionRef> condFuncRef)
29 {
30 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
31 if (ptMethod == nullptr) {
32 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Cannot find MethodLiteral";
33 return false;
34 }
35
36 if (location.GetBytecodeOffset() >= ptMethod->GetCodeSize()) {
37 LOG_DEBUGGER(ERROR) << "SetBreakpoint: Invalid breakpoint location";
38 return false;
39 }
40
41 auto [_, success] = breakpoints_.emplace(location.GetSourceFile(), ptMethod.release(),
42 location.GetBytecodeOffset(), Global<FunctionRef>(ecmaVm_, condFuncRef));
43 if (!success) {
44 // also return true
45 LOG_DEBUGGER(WARN) << "SetBreakpoint: Breakpoint already exists";
46 }
47
48 DumpBreakpoints();
49 return true;
50 }
51
RemoveBreakpoint(const JSPtLocation & location)52 bool JSDebugger::RemoveBreakpoint(const JSPtLocation &location)
53 {
54 std::unique_ptr<PtMethod> ptMethod = FindMethod(location);
55 if (ptMethod == nullptr) {
56 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Cannot find MethodLiteral";
57 return false;
58 }
59
60 if (!RemoveBreakpoint(ptMethod, location.GetBytecodeOffset())) {
61 LOG_DEBUGGER(ERROR) << "RemoveBreakpoint: Breakpoint not found";
62 return false;
63 }
64
65 DumpBreakpoints();
66 return true;
67 }
68
RemoveAllBreakpoints()69 void JSDebugger::RemoveAllBreakpoints()
70 {
71 breakpoints_.clear();
72 }
73
BytecodePcChanged(JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)74 void JSDebugger::BytecodePcChanged(JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
75 {
76 ASSERT(bcOffset < method->GetCodeSize() && "code size of current Method less then bcOffset");
77 HandleExceptionThrowEvent(thread, method, bcOffset);
78
79 // Step event is reported before breakpoint, according to the spec.
80 if (!HandleStep(method, bcOffset)) {
81 HandleBreakpoint(method, bcOffset);
82 }
83 }
84
HandleBreakpoint(JSHandle<Method> method,uint32_t bcOffset)85 bool JSDebugger::HandleBreakpoint(JSHandle<Method> method, uint32_t bcOffset)
86 {
87 auto breakpoint = FindBreakpoint(method, bcOffset);
88 if (hooks_ == nullptr || !breakpoint.has_value()) {
89 return false;
90 }
91
92 JSThread *thread = ecmaVm_->GetJSThread();
93 auto condFuncRef = breakpoint.value().GetConditionFunction();
94 if (condFuncRef->IsFunction()) {
95 LOG_DEBUGGER(INFO) << "HandleBreakpoint: begin evaluate condition";
96 auto handlerPtr = std::make_shared<FrameHandler>(ecmaVm_->GetJSThread());
97 auto res = DebuggerApi::EvaluateViaFuncCall(const_cast<EcmaVM *>(ecmaVm_),
98 condFuncRef.ToLocal(ecmaVm_), handlerPtr);
99 if (thread->HasPendingException()) {
100 LOG_DEBUGGER(ERROR) << "HandleBreakpoint: has pending exception";
101 thread->ClearException();
102 return false;
103 }
104 bool isMeet = res->ToBoolean(ecmaVm_)->Value();
105 if (!isMeet) {
106 LOG_DEBUGGER(ERROR) << "HandleBreakpoint: condition not meet";
107 return false;
108 }
109 }
110
111 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset,
112 breakpoint.value().GetSourceFile()};
113
114 hooks_->Breakpoint(location);
115 return true;
116 }
117
HandleExceptionThrowEvent(const JSThread * thread,JSHandle<Method> method,uint32_t bcOffset)118 void JSDebugger::HandleExceptionThrowEvent(const JSThread *thread, JSHandle<Method> method, uint32_t bcOffset)
119 {
120 if (hooks_ == nullptr || !thread->HasPendingException()) {
121 return;
122 }
123
124 JSPtLocation throwLocation {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
125
126 hooks_->Exception(throwLocation);
127 }
128
HandleStep(JSHandle<Method> method,uint32_t bcOffset)129 bool JSDebugger::HandleStep(JSHandle<Method> method, uint32_t bcOffset)
130 {
131 if (hooks_ == nullptr) {
132 return false;
133 }
134
135 JSPtLocation location {method->GetJSPandaFile(), method->GetMethodId(), bcOffset};
136
137 return hooks_->SingleStep(location);
138 }
139
FindBreakpoint(JSHandle<Method> method,uint32_t bcOffset) const140 std::optional<JSBreakpoint> JSDebugger::FindBreakpoint(JSHandle<Method> method, uint32_t bcOffset) const
141 {
142 for (const auto &bp : breakpoints_) {
143 if ((bp.GetBytecodeOffset() == bcOffset) &&
144 (bp.GetPtMethod()->GetJSPandaFile() == method->GetJSPandaFile()) &&
145 (bp.GetPtMethod()->GetMethodId() == method->GetMethodId())) {
146 return bp;
147 }
148 }
149 return {};
150 }
151
RemoveBreakpoint(const std::unique_ptr<PtMethod> & ptMethod,uint32_t bcOffset)152 bool JSDebugger::RemoveBreakpoint(const std::unique_ptr<PtMethod> &ptMethod, uint32_t bcOffset)
153 {
154 for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
155 const auto &bp = *it;
156 if ((bp.GetBytecodeOffset() == bcOffset) &&
157 (bp.GetPtMethod()->GetJSPandaFile() == ptMethod->GetJSPandaFile()) &&
158 (bp.GetPtMethod()->GetMethodId() == ptMethod->GetMethodId())) {
159 it = breakpoints_.erase(it);
160 return true;
161 }
162 }
163
164 return false;
165 }
166
FindMethod(const JSPtLocation & location) const167 std::unique_ptr<PtMethod> JSDebugger::FindMethod(const JSPtLocation &location) const
168 {
169 std::unique_ptr<PtMethod> ptMethod {nullptr};
170 ::panda::ecmascript::JSPandaFileManager::GetInstance()->EnumerateJSPandaFiles([&ptMethod, location](
171 const panda::ecmascript::JSPandaFile *jsPandaFile) {
172 if (jsPandaFile->GetJSPandaFileDesc() == location.GetJsPandaFile()->GetJSPandaFileDesc()) {
173 MethodLiteral *methodsData = jsPandaFile->GetMethodLiterals();
174 uint32_t numberMethods = jsPandaFile->GetNumMethods();
175 for (uint32_t i = 0; i < numberMethods; ++i) {
176 if (methodsData[i].GetMethodId() == location.GetMethodId()) {
177 MethodLiteral *methodLiteral = methodsData + i;
178 ptMethod = std::make_unique<PtMethod>(jsPandaFile,
179 methodLiteral->GetMethodId(), methodLiteral->IsNativeWithCallField());
180 return false;
181 }
182 }
183 }
184 return true;
185 });
186 return ptMethod;
187 }
188
DumpBreakpoints()189 void JSDebugger::DumpBreakpoints()
190 {
191 LOG_DEBUGGER(INFO) << "dump breakpoints with size " << breakpoints_.size();
192 for (const auto &bp : breakpoints_) {
193 LOG_DEBUGGER(DEBUG) << bp.ToString();
194 }
195 }
196 } // namespace panda::tooling::ecmascript
197