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