/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ECMASCRIPT_TOOLING_AGENT_DEBUGGER_IMPL_H #define ECMASCRIPT_TOOLING_AGENT_DEBUGGER_IMPL_H #include "agent/runtime_impl.h" #include "backend/js_pt_hooks.h" #include "tooling/base/pt_params.h" #include "backend/js_single_stepper.h" #include "dispatcher.h" #include "ecmascript/debugger/js_debugger_manager.h" #include "ecmascript/debugger/js_pt_method.h" #include "libpandabase/macros.h" namespace panda::ecmascript::tooling { namespace test { class TestHooks; } // namespace test enum class DebuggerState { DISABLED, ENABLED, PAUSED }; class DebuggerImpl final { public: DebuggerImpl(const EcmaVM *vm, ProtocolChannel *channel, RuntimeImpl *runtime); ~DebuggerImpl(); // event bool NotifyScriptParsed(const std::string &fileName, std::string_view entryPoint = "func_main_0"); bool SendableScriptParsed(const std::string &fileName, const std::string &url, const std::string &source, const std::string &recordName); bool CheckScriptParsed(const std::string &fileName); bool SendableMethodEntry(JSHandle method); bool MatchUrlAndFileName(const std::string &url, const std::string &fileName); bool NotifySingleStep(const JSPtLocation &location); void NotifyPaused(std::optional location, PauseReason reason); void GeneratePausedInfo(PauseReason reason, std::vector &hitBreakpoints, const Local &exception); bool NotifyNativeOut(); void NotifyHandleProtocolCommand(); void NotifyNativeCalling(const void *nativeAddress); void NotifyNativeReturn(const void *nativeAddress); void NotifyReturnNative(); bool IsUserCode(const void *nativeAddress); void SetDebuggerState(DebuggerState debuggerState); void SetNativeOutPause(bool nativeOutPause); void AddBreakpointDetail(const std::string &url, int32_t lineNumber, std::string *outId, std::vector> *outLocations); DispatchResponse ContinueToLocation(const ContinueToLocationParams ¶ms); DispatchResponse Enable(const EnableParams ¶ms, UniqueDebuggerId *id); DispatchResponse Disable(); DispatchResponse EvaluateOnCallFrame(const EvaluateOnCallFrameParams ¶ms, std::unique_ptr *result); DispatchResponse GetPossibleBreakpoints(const GetPossibleBreakpointsParams ¶ms, std::vector> *outLocations); DispatchResponse GetScriptSource(const GetScriptSourceParams ¶ms, std::string *source); DispatchResponse Pause(); DispatchResponse RemoveBreakpoint(const RemoveBreakpointParams ¶ms); DispatchResponse RemoveBreakpointsByUrl(const RemoveBreakpointsByUrlParams ¶ms); DispatchResponse Resume(const ResumeParams ¶ms); DispatchResponse SetAsyncCallStackDepth(); DispatchResponse SetBreakpointByUrl(const SetBreakpointByUrlParams ¶ms, std::string *outId, std::vector> *outLocations, bool isSmartBreakpoint = false); DispatchResponse SetBreakpointsActive(const SetBreakpointsActiveParams ¶ms); DispatchResponse GetPossibleAndSetBreakpointByUrl(const GetPossibleAndSetBreakpointParams ¶ms, std::vector> &outLocations); DispatchResponse SetPauseOnExceptions(const SetPauseOnExceptionsParams ¶ms); DispatchResponse SetSkipAllPauses(const SetSkipAllPausesParams ¶ms); DispatchResponse SetNativeRange(const SetNativeRangeParams ¶ms); DispatchResponse ResetSingleStepper(const ResetSingleStepperParams ¶ms); DispatchResponse StepInto(const StepIntoParams ¶ms); DispatchResponse SmartStepInto(const SmartStepIntoParams ¶ms); DispatchResponse StepOut(); DispatchResponse StepOver(const StepOverParams ¶ms); DispatchResponse SetBlackboxPatterns(); DispatchResponse SetMixedDebugEnabled(const SetMixedDebugParams ¶ms); DispatchResponse ReplyNativeCalling(const ReplyNativeCallingParams ¶ms); DispatchResponse DropFrame(const DropFrameParams ¶ms); DispatchResponse ClientDisconnect(); DispatchResponse CallFunctionOn( const CallFunctionOnParams ¶ms, std::unique_ptr *outRemoteObject, std::optional> *outExceptionDetails); /** * @brief: match first script and callback * * @return: true means matched and callback execute success */ template bool MatchScripts(const Callback &cb, const std::string &matchStr, ScriptMatchType type) const { for (const auto &script : scripts_) { std::string value; switch (type) { case ScriptMatchType::URL: { value = script.second->GetUrl(); break; } case ScriptMatchType::FILE_NAME: { value = script.second->GetFileName(); break; } case ScriptMatchType::HASH: { value = script.second->GetHash(); break; } default: { return false; } } if (matchStr == value) { return cb(script.second.get()); } } return false; } std::vector MatchAllScripts(const std::string &url) const { std::vector result; for (const auto &script : scripts_) { if (url == script.second->GetUrl()) { result.push_back(script.second.get()); } } return result; } bool GenerateCallFrames(std::vector> *callFrames, bool getScope); class DispatcherImpl final : public DispatcherBase { public: DispatcherImpl(ProtocolChannel *channel, std::unique_ptr debugger) : DispatcherBase(channel), debugger_(std::move(debugger)) {} ~DispatcherImpl() override = default; void ContinueToLocation(const DispatchRequest &request); void Dispatch(const DispatchRequest &request) override; void Enable(const DispatchRequest &request); void Disable(const DispatchRequest &request); void EvaluateOnCallFrame(const DispatchRequest &request); void GetPossibleBreakpoints(const DispatchRequest &request); void GetScriptSource(const DispatchRequest &request); void Pause(const DispatchRequest &request); void RemoveBreakpoint(const DispatchRequest &request); void RemoveBreakpointsByUrl(const DispatchRequest &request); void Resume(const DispatchRequest &request); void SetAsyncCallStackDepth(const DispatchRequest &request); void SetBreakpointByUrl(const DispatchRequest &request); void SetBreakpointsActive(const DispatchRequest &request); void SetPauseOnExceptions(const DispatchRequest &request); void SetSkipAllPauses(const DispatchRequest &request); void SetNativeRange(const DispatchRequest &request); void ResetSingleStepper(const DispatchRequest &request); void StepInto(const DispatchRequest &request); void SmartStepInto(const DispatchRequest &request); void StepOut(const DispatchRequest &request); void StepOver(const DispatchRequest &request); void SetMixedDebugEnabled(const DispatchRequest &request); void SetBlackboxPatterns(const DispatchRequest &request); void ReplyNativeCalling(const DispatchRequest &request); void GetPossibleAndSetBreakpointByUrl(const DispatchRequest &request); void DropFrame(const DispatchRequest &request); void ClientDisconnect(const DispatchRequest &request); void CallFunctionOn(const DispatchRequest &request); enum class Method { CONTINUE_TO_LOCATION, ENABLE, DISABLE, EVALUATE_ON_CALL_FRAME, GET_POSSIBLE_BREAKPOINTS, GET_SCRIPT_SOURCE, PAUSE, REMOVE_BREAKPOINT, REMOVE_BREAKPOINTS_BY_URL, RESUME, SET_ASYNC_CALL_STACK_DEPTH, SET_BREAKPOINT_BY_URL, SET_BREAKPOINTS_ACTIVE, SET_PAUSE_ON_EXCEPTIONS, SET_SKIP_ALL_PAUSES, STEP_INTO, SMART_STEP_INTO, STEP_OUT, STEP_OVER, SET_MIXED_DEBUG_ENABLED, SET_BLACKBOX_PATTERNS, REPLY_NATIVE_CALLING, GET_POSSIBLE_AND_SET_BREAKPOINT_BY_URL, DROP_FRAME, SET_NATIVE_RANGE, RESET_SINGLE_STEPPER, CLIENT_DISCONNECT, CALL_FUNCTION_ON, UNKNOWN }; Method GetMethodEnum(const std::string& method); private: NO_COPY_SEMANTIC(DispatcherImpl); NO_MOVE_SEMANTIC(DispatcherImpl); std::unique_ptr debugger_ {}; }; private: NO_COPY_SEMANTIC(DebuggerImpl); NO_MOVE_SEMANTIC(DebuggerImpl); std::string Trim(const std::string &str); DebugInfoExtractor *GetExtractor(const JSPandaFile *jsPandaFile); std::vector GetExtractors(const std::string &url); std::optional CmptEvaluateValue(CallFrameId callFrameId, const std::string &expression, std::unique_ptr *result); bool GenerateCallFrame(CallFrame *callFrame, const FrameHandler *frameHandler, CallFrameId frameId, bool getScope); void GenerateScopeChains(bool getScope, const FrameHandler *frameHandler, const JSPandaFile *jsPandaFile, std::vector> &scopeChain, std::unique_ptr &thisObj); void SaveCallFrameHandler(const FrameHandler *frameHandler); std::unique_ptr GetLocalScopeChain(const FrameHandler *frameHandler, std::unique_ptr *thisObj); std::unique_ptr GetModuleScopeChain(const FrameHandler *frameHandler); std::unique_ptr GetGlobalScopeChain(const FrameHandler *frameHandler); std::vector> GetClosureScopeChains(const FrameHandler *frameHandler, std::unique_ptr *thisObj); void GetLocalVariables(const FrameHandler *frameHandler, panda_file::File::EntityId methodId, const JSPandaFile *jsPandaFile, Local &thisVal, Local &localObj); void CleanUpOnPaused(); void CleanUpRuntimeProperties(); void UpdateScopeObject(const FrameHandler *frameHandler, std::string_view varName, Local newVal, const std::string& scope); void ClearSingleStepper(); Local ConvertToLocal(const std::string &varValue); bool DecodeAndCheckBase64(const std::string &src, std::vector &dest); bool IsSkipLine(const JSPtLocation &location); bool CheckPauseOnException(); bool IsWithinVariableScope(const LocalVariableInfo &localVariableInfo, uint32_t bcOffset); bool ProcessSingleBreakpoint(const BreakpointInfo &breakpoint, std::vector> &outLocations); bool IsVariableSkipped(const std::string &varName); Local CheckAndGenerateCondFunc(const std::optional &condition); void InitializeExtendedProtocolsList(); const std::unordered_set &GetRecordName(const std::string &url) { static const std::unordered_set recordName; auto iter = recordNames_.find(url); if (iter != recordNames_.end()) { return iter->second; } return recordName; } class Frontend { public: explicit Frontend(ProtocolChannel *channel) : channel_(channel) {} ~Frontend() = default; void BreakpointResolved(const EcmaVM *vm); void Paused(const EcmaVM *vm, const tooling::Paused &paused); void Resumed(const EcmaVM *vm); void NativeCalling(const EcmaVM *vm, const tooling::NativeCalling &nativeCalling); void MixedStack(const EcmaVM *vm, const tooling::MixedStack &mixedStack); void ScriptFailedToParse(const EcmaVM *vm); void ScriptParsed(const EcmaVM *vm, const PtScript &script); void WaitForDebugger(const EcmaVM *vm); void RunIfWaitingForDebugger(const EcmaVM *vm); private: bool AllowNotify(const EcmaVM *vm) const; ProtocolChannel *channel_ {nullptr}; }; const EcmaVM *vm_ {nullptr}; Frontend frontend_; RuntimeImpl *runtime_ {nullptr}; std::unique_ptr hooks_ {nullptr}; JSDebugger *jsDebugger_ {nullptr}; std::unordered_map> recordNames_ {}; std::unordered_map> urlFileNameMap_ {}; std::unordered_map> scripts_ {}; PauseOnExceptionsState pauseOnException_ {PauseOnExceptionsState::NONE}; DebuggerState debuggerState_ {DebuggerState::ENABLED}; bool pauseOnNextByteCode_ {false}; bool breakpointsState_ {true}; bool skipAllPausess_ {false}; bool mixStackEnabled_ {false}; std::unique_ptr singleStepper_ {nullptr}; Location location_ {}; std::unique_ptr nativeOut_ {nullptr}; std::vector nativePointer_; bool nativeOutPause_ {false}; std::vector nativeRanges_ {}; std::unordered_map>> scopeObjects_ {}; std::vector> callFrameHandlers_; JsDebuggerManager::ObjectUpdaterFunc updaterFunc_ {nullptr}; JsDebuggerManager::SingleStepperFunc stepperFunc_ {nullptr}; JsDebuggerManager::ReturnNativeFunc returnNative_ {nullptr}; std::vector debuggerExtendedProtocols_ {}; friend class JSPtHooks; friend class test::TestHooks; friend class DebuggerImplFriendTest; }; } // namespace panda::ecmascript::tooling #endif