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