diff --git a/runtime/tooling/inspector/BUILD.gn b/runtime/tooling/inspector/BUILD.gn index f03a90b21be3d3f155f32c24d139774610652e86..602d587733581c23273efa3648dc01e2a655d3bb 100644 --- a/runtime/tooling/inspector/BUILD.gn +++ b/runtime/tooling/inspector/BUILD.gn @@ -25,8 +25,11 @@ libarkinspector_sources = [ "session_manager.cpp", "source_manager.cpp", "thread_state.cpp", + "types/call_frame.cpp", + "types/execution_context_description.cpp", "types/location.cpp", "types/remote_object.cpp", + "types/scope.cpp", "ws_logger.cpp", ] diff --git a/runtime/tooling/inspector/CMakeLists.txt b/runtime/tooling/inspector/CMakeLists.txt index 7c812e09719708fb985128e9601b1d5d5a7d54ab..b26c7a95700e8e2252ea84cb02f0f39b6d131f92 100644 --- a/runtime/tooling/inspector/CMakeLists.txt +++ b/runtime/tooling/inspector/CMakeLists.txt @@ -31,8 +31,11 @@ add_library(arkinspector SHARED session_manager.cpp source_manager.cpp thread_state.cpp + types/call_frame.cpp + types/execution_context_description.cpp types/location.cpp types/remote_object.cpp + types/scope.cpp ws_logger.cpp ) diff --git a/runtime/tooling/inspector/asio_server.h b/runtime/tooling/inspector/asio_server.h index f23e05548028dbeb8f1d4e01203f0f9bf10b1679..ebfe98f2818f4324b733a1a8528eb4c855f321a3 100644 --- a/runtime/tooling/inspector/asio_server.h +++ b/runtime/tooling/inspector/asio_server.h @@ -27,6 +27,12 @@ namespace panda::tooling::inspector { // NOLINTNEXTLINE(fuchsia-multiple-inheritance) class AsioServer final : public ServerEndpoint { public: + bool Kill() override + { + endpoint_.stop(); + return ServerEndpoint::Kill(); + } + bool Poll() override { return endpoint_.poll() != 0; diff --git a/runtime/tooling/inspector/debug_info_cache.cpp b/runtime/tooling/inspector/debug_info_cache.cpp index 4178e5ada91865985aebf4ab98677ad68d4c6830..b0040e824b309889a9a1bded31dcad4c221ea82a 100644 --- a/runtime/tooling/inspector/debug_info_cache.cpp +++ b/runtime/tooling/inspector/debug_info_cache.cpp @@ -17,11 +17,14 @@ #include "debug_info_extractor.h" #include "disassembler/disasm_backed_debug_info_extractor.h" +#include "macros.h" #include "optimizer/ir_builder/inst_builder.h" #include "tooling/pt_location.h" #include "utils/logger.h" #include "utils/utf.h" +#include + namespace panda::tooling::inspector { DebugInfoCache::DebugInfoCache(bool back_debug_info_with_disasm) : back_debug_info_with_disasm_(back_debug_info_with_disasm) @@ -43,6 +46,20 @@ void DebugInfoCache::AddPandaFile(const panda_file::File &file) : std::make_unique(&file)); } +std::string_view DebugInfoCache::GetSourceFile(const PtLocation &location) +{ + std::string_view source_file; + EnumerateLineEntries([&](auto file, auto &) { return file->GetFilename() == location.GetPandaFile(); }, + [&](auto, auto &debug_info, auto method_id) { + if (method_id == location.GetMethodId()) { + source_file = debug_info->GetSourceFile(method_id); + } + return false; + }, + [](auto &&...) { return false; }); + return source_file; +} + void DebugInfoCache::GetSourceLocation(const PtFrame &frame, std::string_view &source_file, std::string_view &method_name, size_t &line_number) { @@ -127,24 +144,20 @@ std::unordered_set DebugInfoCache::GetContinueToLocati } std::vector DebugInfoCache::GetBreakpointLocations( - const std::function &source_file_filter, size_t line_number, - std::set &source_files) + const std::function &source_file_filter, size_t line_number) { std::vector locations; - source_files.clear(); - EnumerateLineEntries([](auto, auto &) { return true; }, - [&source_file_filter](auto, auto &debug_info, auto method_id) { - return source_file_filter(debug_info->GetSourceFile(method_id)); - }, - [line_number, &source_files, &locations](auto panda_file, auto &debug_info, auto method_id, - auto &entry, auto /* next */) { - if (entry.line == line_number) { - source_files.insert(debug_info->GetSourceFile(method_id)); - locations.emplace_back(panda_file->GetFilename().data(), method_id, entry.offset); - } - - return true; - }); + EnumerateLineEntries( + [](auto, auto &) { return true; }, + [&source_file_filter](auto, auto &debug_info, auto method_id) { + return source_file_filter(debug_info->GetSourceFile(method_id)); + }, + [line_number, &locations](auto panda_file, auto &, auto method_id, auto &entry, auto /* next */) { + if (entry.line == line_number) { + locations.emplace_back(panda_file->GetFilename().data(), method_id, entry.offset); + } + return true; + }); return locations; } diff --git a/runtime/tooling/inspector/debug_info_cache.h b/runtime/tooling/inspector/debug_info_cache.h index 0df02febe560a4c172a0700a8c42890e3e875cbd..f3e483b39265fe8f4731e466881df88fd06029b7 100644 --- a/runtime/tooling/inspector/debug_info_cache.h +++ b/runtime/tooling/inspector/debug_info_cache.h @@ -20,9 +20,11 @@ #include "method.h" #include "tooling/debugger.h" +#include "tooling/pt_location.h" #include #include +#include #include #include #include @@ -37,13 +39,14 @@ public: NO_MOVE_SEMANTIC(DebugInfoCache); void AddPandaFile(const panda_file::File &file); + std::string_view GetSourceFile(const PtLocation &location); void GetSourceLocation(const PtFrame &frame, std::string_view &source_file, std::string_view &method_name, size_t &line_number); std::unordered_set GetCurrentLineLocations(const PtFrame &frame); std::unordered_set GetContinueToLocations(std::string_view source_file, size_t line_number); std::vector GetBreakpointLocations(const std::function &source_file_filter, - size_t line_number, std::set &source_files); + size_t line_number); std::set GetValidLineNumbers(std::string_view source_file, size_t start_line, size_t end_line, bool restrict_to_function); diff --git a/runtime/tooling/inspector/endpoint.h b/runtime/tooling/inspector/endpoint.h index 15cacc1692ca71242855e440b3ccd4a4328fa209..c1c064f30a0c0d006cdcc963ed2f4ff276aaff16 100644 --- a/runtime/tooling/inspector/endpoint.h +++ b/runtime/tooling/inspector/endpoint.h @@ -128,13 +128,15 @@ private: template void Send(BuildFunction &&build) { + if (!connection_) { + return; + } + JsonObjectBuilder builder; build(builder); auto message = std::move(builder).Build(); LOG(DEBUG, DEBUGGER) << "Sending " << message; - if (auto connection = GetPinnedConnection()) { - connection->send(message, websocketpp::frame::opcode::text); - } + connection_->send(message, websocketpp::frame::opcode::text); } typename WsEndpoint::connection_ptr connection_; diff --git a/runtime/tooling/inspector/event_loop.h b/runtime/tooling/inspector/event_loop.h index 95d1b74307b859ee79fa16390a08042f8404aa41..436a09aecd3bcb0021e92be3b5484c2c10d2013a 100644 --- a/runtime/tooling/inspector/event_loop.h +++ b/runtime/tooling/inspector/event_loop.h @@ -24,7 +24,7 @@ namespace panda::tooling::inspector { class EventLoop { public: // Notify the running event loop to stop. - bool Kill(); + virtual bool Kill(); // Execute ready event handlers without blocking. virtual bool Poll() = 0; diff --git a/runtime/tooling/inspector/inspector.cpp b/runtime/tooling/inspector/inspector.cpp index af9c49888696f2a0dc287dd5fb31726e61226de8..7f743152d594be90ed793f6f6f8d24596c22682b 100644 --- a/runtime/tooling/inspector/inspector.cpp +++ b/runtime/tooling/inspector/inspector.cpp @@ -16,22 +16,32 @@ #include "inspector.h" #include "error.h" +#include "thread_state.h" +#include "types/call_frame.h" +#include "types/location.h" +#include "types/numeric_id.h" +#include "types/scope.h" #include "macros.h" +#include "managed_thread.h" #include "plugins.h" #include "runtime.h" #include "source_lang_enum.h" #include "thread.h" #include "tooling/inspector/remote_object.h" +#include "tooling/pt_thread.h" #include "tooling/vreg_value.h" #include "utils/logger.h" #include #include -#include #include #include +#include +#include #include +#include +#include #include #include @@ -58,7 +68,7 @@ Inspector::Inspector(Server &server, DebugInterface &debugger, std::unique_ptrGetClass()->GetSourceLang(); + auto extension = GetExtension(source_lang); + if (extension == nullptr) { + LOG(WARNING, DEBUGGER) << "Could not convert exception object, please define InspectorExtension for " + << plugins::LangToRuntimeType(source_lang); + return; + } + + FindThreadState( + thread, // NOLINTNEXTLINE(modernize-avoid-bind) + std::bind(&ThreadState::OnException, _1, std::ref(location), + extension->GetRemoteObject(thread.GetManagedThread(), object_repository_, exception_object)), + true); +} + +void Inspector::ExceptionCatch(PtThread thread, Method * /* catch_method */, const PtLocation &location, + ObjectHeader * /* exception_object */) { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); - it->second.OnFramePop(); + // NOLINTNEXTLINE(modernize-avoid-bind) + FindThreadState(thread, std::bind(&ThreadState::OnExceptionCatch, _1, std::ref(location)), true); +} + +void Inspector::FramePop(PtThread thread, Method * /* method */, bool /* was_popped_by_exception */) +{ + os::memory::ReadLockHolder lock(debugger_events_lock_); + FindThreadState(thread, &ThreadState::OnFramePop, true); } void Inspector::MethodEntry(PtThread thread, Method * /* method */) { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); - if (it->second.OnMethodEntry()) { - HandleError(debugger_.NotifyFramePop(thread, 0)); - } + FindThreadState( + thread, + [&](auto &state) { + if (state.OnMethodEntry()) { + HandleError(debugger_.NotifyFramePop(thread, 0)); + } + }, + true); } void Inspector::LoadModule(std::string_view file_name) @@ -159,9 +199,8 @@ void Inspector::SingleStep(PtThread thread, Method * /* method */, const PtLocat { os::memory::ReadLockHolder lock(debugger_events_lock_); - auto it = states_.find(thread); - ASSERT(it != states_.end()); - it->second.OnSingleStep(location); + // NOLINTNEXTLINE(modernize-avoid-bind) + FindThreadState(thread, std::bind(&ThreadState::OnSingleStep, _1, std::ref(location)), true); } void Inspector::ThreadStart(PtThread thread) @@ -172,44 +211,21 @@ void Inspector::ThreadStart(PtThread thread) inspector_server_.CallTargetAttachedToTarget(thread); } - auto [it, inserted] = states_.emplace( - std::piecewise_construct, std::forward_as_tuple(thread), - std::forward_as_tuple( - [this, thread](auto &hit_breakpoints) { - Suspend(thread); - inspector_server_.CallDebuggerPaused(thread, hit_breakpoints, [this, thread](auto &handler) { - std::deque scope_chain; - - auto res = HandleError(debugger_.EnumerateFrames(thread, [&](auto &frame) { - scope_chain.push_back(GetFrameObject(thread, frame)); - return true; - })); - if (!res) { - return; - } - - HandleError(debugger_.EnumerateFrames(thread, [this, &handler, &scope_chain](const PtFrame &frame) { - std::string_view source_file; - std::string_view method_name; - size_t line_number; - debug_info_cache_.GetSourceLocation(frame, source_file, method_name, line_number); - - handler(frame.GetFrameId(), method_name, source_file, line_number, scope_chain); - - scope_chain.pop_front(); - return true; - })); - }); - }, - [this, thread]() NO_THREAD_SAFETY_ANALYSIS { - debugger_events_lock_.Unlock(); - WaitSuspension(thread); - debugger_events_lock_.ReadLock(); - }, - [this, thread]() { - Resume(thread); - inspector_server_.CallDebuggerResumed(thread); - })); + auto [it, inserted] = states_.try_emplace( + thread, std::bind(&Inspector::GetCallStack, this, thread), // NOLINT(modernize-avoid-bind) + [this, thread](auto &call_stack, auto &hit_breakpoints, auto exception) { + Suspend(thread); + inspector_server_.CallDebuggerPaused(thread, hit_breakpoints, std::move(exception), call_stack); + }, + [this, thread]() NO_THREAD_SAFETY_ANALYSIS { + debugger_events_lock_.Unlock(); + WaitSuspension(thread); + debugger_events_lock_.ReadLock(); + }, + [this, thread]() { + Resume(thread); + inspector_server_.CallDebuggerResumed(thread); + }); (void)inserted; ASSERT(inserted); @@ -222,74 +238,113 @@ void Inspector::ThreadEnd(PtThread thread) { os::memory::ReadLockHolder lock(debugger_events_lock_); + FindThreadState(thread, &ThreadState::OnThreadEnd); + if (thread != PtThread::NONE) { inspector_server_.CallTargetDetachedFromTarget(thread); + source_manager_.RemoveThread(thread); } states_.erase(thread); } +void Inspector::VmInitialization(PtThread thread) +{ + ASSERT(!execution_context_); + execution_context_.emplace(thread); +} + +void Inspector::VmDeath() +{ + os::memory::ReadLockHolder lock(debugger_events_lock_); + + ASSERT(execution_context_); + inspector_server_.CallRuntimeExecutionContextDestroyed(PtThread(ManagedThread::GetCurrent()), *execution_context_); + execution_context_.reset(); + + for (auto &state : states_) { + state.second.OnThreadEnd(); + } + states_.clear(); +} + +void Inspector::FindThreadState(PtThread thread, const std::function &handler, bool assert_found) +{ + if (auto it = states_.find(thread); it != states_.end()) { + handler(it->second); + } else if (assert_found) { + UNREACHABLE(); + } +} + void Inspector::RuntimeEnable(PtThread thread) { - inspector_server_.CallRuntimeExecutionContextCreated(thread); + ASSERT(execution_context_); + inspector_server_.CallRuntimeExecutionContextCreated(thread, *execution_context_); } void Inspector::RunIfWaitingForDebugger(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.Touch(); - } + FindThreadState(thread, &ThreadState::Touch); } void Inspector::Pause(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.Pause(); - } + FindThreadState(thread, &ThreadState::Pause); } void Inspector::Continue(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.Continue(); - } + FindThreadState(thread, &ThreadState::Continue); } void Inspector::SetBreakpointsActive(PtThread thread, bool active) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.SetBreakpointsActive(active); - } + // NOLINTNEXTLINE(modernize-avoid-bind) + FindThreadState(thread, std::bind(&ThreadState::SetBreakpointsActive, _1, active)); } -std::set Inspector::GetPossibleBreakpoints(std::string_view source_file, size_t start_line, size_t end_line, +std::set Inspector::GetPossibleBreakpoints(ScriptId script_id, size_t start_line, size_t end_line, bool restrict_to_function) { + auto source_file = source_manager_.GetSourceFileName(script_id); return debug_info_cache_.GetValidLineNumbers(source_file, start_line, end_line, restrict_to_function); } -std::optional Inspector::SetBreakpoint(PtThread thread, - const std::function &source_files_filter, - size_t line_number, std::set &source_files) +std::optional>> Inspector::SetBreakpoint( + PtThread thread, const std::function &source_file_filter, size_t line_number) { - if (auto it = states_.find(thread); it != states_.end()) { - auto locations = debug_info_cache_.GetBreakpointLocations(source_files_filter, line_number, source_files); - return it->second.SetBreakpoint(locations); - } + std::optional>> result; - return {}; + FindThreadState(thread, [&](auto &state) { + auto pt_locations = debug_info_cache_.GetBreakpointLocations( + [&](auto file_name) { return source_file_filter(GetScriptId(thread, file_name), file_name); }, line_number); + + result.emplace(); + result->first = state.SetBreakpoint(pt_locations); + + std::unordered_set script_ids; + for (auto &pt_location : pt_locations) { + script_ids.insert(GetScriptId(thread, debug_info_cache_.GetSourceFile(pt_location))); + } + + std::transform(script_ids.begin(), script_ids.end(), std::back_inserter(result->second), + [&](auto script_id) { return Location(script_id, line_number); }); + }); + + return result; } void Inspector::RemoveBreakpoint(PtThread thread, BreakpointId id) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.RemoveBreakpoint(id); - } + // NOLINTNEXTLINE(modernize-avoid-bind) + FindThreadState(thread, std::bind(&ThreadState::RemoveBreakpoint, _1, id)); +} + +void Inspector::SetPauseOnExceptions(PtThread thread, ThreadState::PauseOnExceptions state) +{ + // NOLINTNEXTLINE(modernize-avoid-bind) + FindThreadState(thread, std::bind(&ThreadState::SetPauseOnExceptions, _1, state)); } void Inspector::StepIntoOver(PtThread thread, ThreadState::StepKind step_kind) @@ -297,33 +352,84 @@ void Inspector::StepIntoOver(PtThread thread, ThreadState::StepKind step_kind) // NOLINTNEXTLINE(readability-simplify-boolean-expr) ASSERT(step_kind == ThreadState::StepKind::STEP_INTO || step_kind == ThreadState::StepKind::STEP_OVER); - auto it = states_.find(thread); - if (it != states_.end()) { + FindThreadState(thread, [&](auto &state) { + if (!execution_context_) { + state.Continue(); + return; + } + auto frame = debugger_.GetCurrentFrame(thread); if (!frame) { HandleError(frame.Error()); return; } - it->second.StepIntoOver(step_kind, debug_info_cache_.GetCurrentLineLocations(*frame.Value())); - } + state.StepIntoOver(step_kind, debug_info_cache_.GetCurrentLineLocations(**frame)); + }); } void Inspector::StepOut(PtThread thread) { - auto it = states_.find(thread); - if (it != states_.end()) { + FindThreadState(thread, [&](auto &state) { + if (!execution_context_) { + state.Continue(); + return; + } + HandleError(debugger_.NotifyFramePop(thread, 0)); - it->second.StepOut(); - } + state.StepOut(); + }); } -void Inspector::ContinueToLocation(PtThread thread, std::string_view source_file, size_t line_number) +void Inspector::ContinueToLocation(PtThread thread, const Location &location) { - auto it = states_.find(thread); - if (it != states_.end()) { - it->second.ContinueTo(debug_info_cache_.GetContinueToLocations(source_file, line_number)); + FindThreadState(thread, [&](auto &state) { + if (!execution_context_) { + state.Continue(); + return; + } + + auto source_file = source_manager_.GetSourceFileName(location.GetScriptId()); + auto locations = debug_info_cache_.GetContinueToLocations(source_file, location.GetLineNumber()); + state.ContinueTo(std::move(locations)); + }); +} + +std::shared_ptr Inspector::GetCallStack(PtThread thread) +{ + std::function(std::shared_ptr)> build_scope_chain = [](auto s) { return s; }; + if (!HandleError(debugger_.EnumerateFrames(thread, [&](auto &frame) { + auto object = GetFrameObject(thread, frame); + build_scope_chain = [build = std::move(build_scope_chain), object = std::move(object)](auto next) mutable { + return build(std::make_shared(std::move(object), std::move(next))); + }; + return true; + }))) { + return {}; } + + auto scope_chain = build_scope_chain(nullptr); + + std::function(std::shared_ptr)> build_call_stack = [](auto s) { return s; }; + HandleError(debugger_.EnumerateFrames(thread, [&](auto &frame) { + std::string_view source_file; + std::string_view method_name; + size_t line_number; + debug_info_cache_.GetSourceLocation(frame, source_file, method_name, line_number); + + Location location(GetScriptId(thread, source_file), line_number); + + build_call_stack = [build = std::move(build_call_stack), id = frame.GetFrameId(), method_name, location, + source_file, scope_chain](auto next) mutable { + return build(std::make_shared(id, method_name, location, source_file, std::move(scope_chain), + std::move(next))); + }; + + scope_chain = scope_chain->GetNext(); + return true; + })); + + return build_call_stack(nullptr); } InspectorExtension *Inspector::GetExtension(panda_file::SourceLang source_lang) @@ -358,7 +464,7 @@ RemoteObject Inspector::GetFrameObject(PtThread thread, const PtFrame &frame) } } else if (auto extension = GetExtension(source_lang)) { auto parameters = debug_info_cache_.GetParameterInfo(method); - ASSERT(parameters.empty() || parameters.size() == frame.GetArgumentNum()); + ASSERT(parameters.size() <= frame.GetArgumentNum()); for (auto parameter_num = 0U; parameter_num < parameters.size(); ++parameter_num) { auto parameter = parameters[parameter_num]; @@ -404,9 +510,20 @@ std::vector Inspector::GetProperties(RemoteObjectId object_i return *properties; } -std::string Inspector::GetSourceCode(std::string_view source_file) +ScriptId Inspector::GetScriptId(PtThread thread, std::string_view file_name) +{ + auto [script_id, is_new] = source_manager_.GetScriptId(thread, file_name); + + if (is_new) { + inspector_server_.CallDebuggerScriptParsed(thread, script_id, file_name); + } + + return script_id; +} + +std::string Inspector::GetSourceCode(ScriptId script_id) { - return debug_info_cache_.GetSourceCode(source_file); + return debug_info_cache_.GetSourceCode(source_manager_.GetSourceFileName(script_id)); } InspectorST::InspectorST(Server &server, DebugInterface &debugger) diff --git a/runtime/tooling/inspector/inspector.h b/runtime/tooling/inspector/inspector.h index cf41a29328ff699f609ad0498d4cf3255c4b676e..6853bc75835d341cd3e9912627f5200a2d44c96a 100644 --- a/runtime/tooling/inspector/inspector.h +++ b/runtime/tooling/inspector/inspector.h @@ -18,7 +18,10 @@ #include "debug_info_cache.h" #include "inspector_server.h" +#include "source_manager.h" #include "thread_state.h" +#include "types/execution_context_description.h" +#include "types/location.h" #include "types/numeric_id.h" #include "source_lang_enum.h" @@ -38,14 +41,15 @@ #include #include #include +#include #include namespace panda::tooling { class DebugInterface; namespace inspector { -// NOLINTNEXTLINE(fuchsia-virtual-inheritance) -class Server; +class CallFrame; +class Server; // NOLINT(fuchsia-virtual-inheritance) class Inspector : public PtHooks { public: @@ -58,14 +62,22 @@ public: void ConsoleCall(PtThread thread, ConsoleCallType type, uint64_t timestamp, const PandaVector &arguments) override; + void Exception(PtThread thread, Method *method, const PtLocation &location, ObjectHeader *exception_object, + Method *catch_method, const PtLocation &catch_location) override; + void ExceptionCatch(PtThread thread, Method *catch_method, const PtLocation &location, + ObjectHeader *exception_object) override; void FramePop(PtThread thread, Method *method, bool was_popped_by_exception) override; void MethodEntry(PtThread thread, Method *method) override; void LoadModule(std::string_view file_name) override; void SingleStep(PtThread thread, Method *method, const PtLocation &location) override; void ThreadStart(PtThread thread) override; void ThreadEnd(PtThread thread) override; + void VmInitialization(PtThread thread) override; + void VmDeath() override; private: + void FindThreadState(PtThread thread, const std::function &handler, bool assert_found = false); + void RuntimeEnable(PtThread thread); void RunIfWaitingForDebugger(PtThread thread); @@ -74,21 +86,24 @@ private: void Continue(PtThread thread); void SetBreakpointsActive(PtThread thread, bool active); - std::set GetPossibleBreakpoints(std::string_view source_file, size_t start_line, size_t end_line, + std::set GetPossibleBreakpoints(ScriptId script_id, size_t start_line, size_t end_line, bool restrict_to_function); - std::optional SetBreakpoint(PtThread thread, - const std::function &source_files_filter, - size_t line_number, std::set &source_files); + std::optional>> SetBreakpoint( + PtThread thread, const std::function &source_file_filter, size_t line_number); void RemoveBreakpoint(PtThread thread, BreakpointId breakpoint_id); + void SetPauseOnExceptions(PtThread thread, ThreadState::PauseOnExceptions state); + void StepIntoOver(PtThread thread, ThreadState::StepKind step_kind); void StepOut(PtThread thread); - void ContinueToLocation(PtThread thread, std::string_view source_file, size_t line_number); + void ContinueToLocation(PtThread thread, const Location &location); + std::shared_ptr GetCallStack(PtThread thread); InspectorExtension *GetExtension(panda_file::SourceLang source_lang); RemoteObject GetFrameObject(PtThread thread, const PtFrame &frame); std::vector GetProperties(RemoteObjectId object_id, bool generate_preview); - std::string GetSourceCode(std::string_view source_file); + ScriptId GetScriptId(PtThread thread, std::string_view file_name); + std::string GetSourceCode(ScriptId script_id); virtual void Suspend(PtThread thread) = 0; virtual void WaitSuspension(PtThread thread) = 0; @@ -106,7 +121,9 @@ private: DebugInterface &debugger_; DebugInfoCache debug_info_cache_; std::map states_; + SourceManager source_manager_; ObjectRepository object_repository_; + std::optional execution_context_; std::array>, panda_file::LANG_COUNT> extensions_; }; diff --git a/runtime/tooling/inspector/inspector_server.cpp b/runtime/tooling/inspector/inspector_server.cpp index bde6aa4ce5f9be15a6a620e8af718283e5686f64..f1a703b4f84599b2838ac82bbc7d3fa3052dd241 100644 --- a/runtime/tooling/inspector/inspector_server.cpp +++ b/runtime/tooling/inspector/inspector_server.cpp @@ -16,20 +16,24 @@ #include "inspector_server.h" #include "server.h" +#include "thread_state.h" +#include "types/call_frame.h" +#include "types/execution_context_description.h" #include "types/location.h" #include "types/numeric_id.h" #include "console_call_type.h" #include "macros.h" #include "tooling/inspector/remote_object_id.h" -#include "tooling/pt_thread.h" #include "utils/json_builder.h" #include "utils/json_parser.h" #include "utils/logger.h" +#include #include #include #include +#include #include namespace panda::tooling::inspector { @@ -65,8 +69,7 @@ void InspectorServer::OnOpen(std::function &&handler) { server_.OnOpen([this, handler = std::move(handler)]() { // A new connection is open, reinitialize the state - session_manager_->EnumerateSessions([this](auto &id, auto thread) { - source_manager_.RemoveThread(thread); + session_manager_->EnumerateSessions([this](auto &id, auto) { if (!id.empty()) { SendTargetAttachedToTarget(id); } @@ -85,39 +88,17 @@ void InspectorServer::OnFail(std::function &&handler) }); } -void InspectorServer::CallDebuggerPaused( - PtThread thread, const std::vector &hit_breakpoints, - const std::function &)> &)> &enumerate_frames) +void InspectorServer::CallDebuggerPaused(PtThread thread, const std::vector &hit_breakpoints, + std::optional exception, + const std::shared_ptr &call_stack) { auto session_id = session_manager_->GetSessionIdByThread(thread); server_.Call(session_id, "Debugger.paused", [&](auto ¶ms) { - params.AddProperty("callFrames", [this, thread, &enumerate_frames](JsonArrayBuilder &call_frames) { - enumerate_frames([this, thread, &call_frames](auto frame_id, auto method_name, auto source_file, - auto line_number, auto &scope_chain) { - call_frames.Add([&](JsonObjectBuilder &call_frame) { - auto [script_id, is_new] = source_manager_.GetScriptId(thread, source_file); - - if (is_new) { - CallDebuggerScriptParsed(thread, script_id, source_file); - } - - call_frame.AddProperty("callFrameId", std::to_string(frame_id)); - call_frame.AddProperty("functionName", method_name.data()); - call_frame.AddProperty("location", Location(script_id, line_number).ToJson()); - call_frame.AddProperty("url", source_file.data()); - - call_frame.AddProperty("scopeChain", [&](JsonArrayBuilder &scope_chain_builder) { - for (auto &scope : scope_chain) { - scope_chain_builder.Add([&](JsonObjectBuilder &scope_builder) { - scope_builder.AddProperty("type", "local"); - scope_builder.AddProperty("object", scope.ToJson()); - }); - } - }); - }); - }); + params.AddProperty("callFrames", [&](JsonArrayBuilder &call_stack_builder) { + for (auto call_frame = call_stack; call_frame; call_frame = call_frame->GetNext()) { + call_stack_builder.Add(call_frame->ToJson()); + } }); params.AddProperty("hitBreakpoints", [&hit_breakpoints](JsonArrayBuilder &hit_breakpoints_builder) { @@ -126,7 +107,11 @@ void InspectorServer::CallDebuggerPaused( } }); - params.AddProperty("reason", "other"); + if (exception) { + params.AddProperty("data", exception->ToJson()); + } + + params.AddProperty("reason", exception ? "exception" : "other"); }); } @@ -187,22 +172,22 @@ void InspectorServer::CallRuntimeConsoleApiCalled(PtThread thread, ConsoleCallTy }); } -void InspectorServer::CallRuntimeExecutionContextCreated(PtThread thread) +void InspectorServer::CallRuntimeExecutionContextCreated(PtThread thread, + const ExecutionContextDescription &execution_context) { auto session_id = session_manager_->GetSessionIdByThread(thread); - std::string name; - if (thread != PtThread::NONE) { - name = "Thread #" + std::to_string(thread.GetId()); - } + server_.Call(session_id, "Runtime.executionContextCreated", + [&](auto ¶ms) { params.AddProperty("context", execution_context.ToJson()); }); +} - server_.Call(session_id, "Runtime.executionContextCreated", [&](auto ¶ms) { - params.AddProperty("context", [&](JsonObjectBuilder &context) { - context.AddProperty("id", thread.GetId()); - context.AddProperty("origin", ""); - context.AddProperty("name", name); - }); - }); +void InspectorServer::CallRuntimeExecutionContextDestroyed(PtThread thread, + const ExecutionContextDescription &execution_context) +{ + auto session_id = session_manager_->GetSessionIdByThread(thread); + + server_.Call(session_id, "Runtime.executionContextDestroyed", + [&](auto ¶ms) { params.AddProperty("executionContextId", execution_context.GetId()); }); } void InspectorServer::CallTargetAttachedToTarget(PtThread thread) @@ -221,7 +206,6 @@ void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) server_.Pause(); session_manager_->RemoveSession(session_id); - source_manager_.RemoveThread(thread); // Now no one will retrieve the detached thread from the sessions manager server_.Continue(); @@ -232,28 +216,25 @@ void InspectorServer::CallTargetDetachedFromTarget(PtThread thread) } } -void InspectorServer::OnCallDebuggerContinueToLocation( - std::function &&handler) +void InspectorServer::OnCallDebuggerContinueToLocation(std::function &&handler) { - server_.OnCall( - "Debugger.continueToLocation", [this, handler = std::move(handler)](auto &session_id, auto &, auto ¶ms) { - auto location = Location::FromJsonProperty(params, "location"); - if (!location) { - LOG(INFO, DEBUGGER) << location.Error(); - return; - } - - auto thread = session_manager_->GetThreadBySessionId(session_id); + server_.OnCall("Debugger.continueToLocation", + [this, handler = std::move(handler)](auto &session_id, auto &, auto ¶ms) { + auto location = Location::FromJsonProperty(params, "location"); + if (!location) { + LOG(INFO, DEBUGGER) << location.Error(); + return; + } - handler(thread, source_manager_.GetSourceFileName(location->GetScriptId()), location->GetLineNumber()); - }); + handler(session_manager_->GetThreadBySessionId(session_id), *location); + }); } void InspectorServer::OnCallDebuggerGetPossibleBreakpoints( - std::function(std::string_view, size_t, size_t, bool)> &&handler) + std::function(ScriptId, size_t, size_t, bool)> &&handler) { server_.OnCall("Debugger.getPossibleBreakpoints", - [this, handler = std::move(handler)](auto &, auto &result, const JsonObject ¶ms) { + [handler = std::move(handler)](auto &, auto &result, const JsonObject ¶ms) { auto start = Location::FromJsonProperty(params, "start"); if (!start) { LOG(INFO, DEBUGGER) << start.Error(); @@ -277,8 +258,7 @@ void InspectorServer::OnCallDebuggerGetPossibleBreakpoints( restrict_to_function = *prop; } - auto line_numbers = handler(source_manager_.GetSourceFileName(script_id), start->GetLineNumber(), - end_line, restrict_to_function); + auto line_numbers = handler(script_id, start->GetLineNumber(), end_line, restrict_to_function); result.AddProperty("locations", [script_id, &line_numbers](JsonArrayBuilder &array) { for (auto line_number : line_numbers) { @@ -288,17 +268,15 @@ void InspectorServer::OnCallDebuggerGetPossibleBreakpoints( }); } -void InspectorServer::OnCallDebuggerGetScriptSource(std::function &&handler) +void InspectorServer::OnCallDebuggerGetScriptSource(std::function &&handler) { - server_.OnCall("Debugger.getScriptSource", - [this, handler = std::move(handler)](auto &, auto &result, auto ¶ms) { - if (auto script_id = ParseNumericId(params, "scriptId")) { - auto source_file = source_manager_.GetSourceFileName(*script_id); - result.AddProperty("scriptSource", handler(source_file)); - } else { - LOG(INFO, DEBUGGER) << script_id.Error(); - } - }); + server_.OnCall("Debugger.getScriptSource", [handler = std::move(handler)](auto &, auto &result, auto ¶ms) { + if (auto script_id = ParseNumericId(params, "scriptId")) { + result.AddProperty("scriptSource", handler(*script_id)); + } else { + LOG(INFO, DEBUGGER) << script_id.Error(); + } + }); } void InspectorServer::OnCallDebuggerPause(std::function &&handler) @@ -330,38 +308,36 @@ void InspectorServer::OnCallDebuggerResume(std::function &&handl } void InspectorServer::OnCallDebuggerSetBreakpoint( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler) + std::function>>( + PtThread, const std::function &, size_t)> &&handler) { - server_.OnCall("Debugger.setBreakpoint", - [this, handler = std::move(handler)](auto &session_id, auto &result, auto ¶ms) { - auto location = Location::FromJsonProperty(params, "location"); - if (!location) { - LOG(INFO, DEBUGGER) << location.Error(); - return; - } - - auto thread = session_manager_->GetThreadBySessionId(session_id); + server_.OnCall( + "Debugger.setBreakpoint", [this, handler = std::move(handler)](auto &session_id, auto &result, auto ¶ms) { + auto location = Location::FromJsonProperty(params, "location"); + if (!location) { + LOG(INFO, DEBUGGER) << location.Error(); + return; + } - auto source_file = source_manager_.GetSourceFileName(location->GetScriptId()); - std::set source_files; + auto thread = session_manager_->GetThreadBySessionId(session_id); + auto source_file_filter = [&](auto script_id, auto) { return script_id == location->GetScriptId(); }; - auto id = handler( - thread, [source_file](auto file_name) { return file_name == source_file; }, - location->GetLineNumber(), source_files); - if (!id) { - LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; - return; - } + BreakpointId id; + if (auto breakpoint = handler(thread, source_file_filter, location->GetLineNumber())) { + id = breakpoint->first; + } else { + LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; + return; + } - result.AddProperty("breakpointId", std::to_string(*id)); - result.AddProperty("actualLocation", location->ToJson()); - }); + result.AddProperty("breakpointId", std::to_string(id)); + result.AddProperty("actualLocation", location->ToJson()); + }); } void InspectorServer::OnCallDebuggerSetBreakpointByUrl( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler) + std::function>>( + PtThread, const std::function &, size_t)> &&handler) { server_.OnCall("Debugger.setBreakpointByUrl", [this, handler = std::move(handler)](auto &session_id, auto &result, const JsonObject ¶ms) { @@ -373,12 +349,16 @@ void InspectorServer::OnCallDebuggerSetBreakpointByUrl( return; } - std::function source_file_filter; + std::function source_file_filter; if (auto url = params.GetValue("url")) { - source_file_filter = [source_file = url->find("file://") == 0 ? url->substr(std::strlen("file://")) : *url]( - auto file_name) { return file_name == source_file; }; + std::string_view source_file(*url); + if (url->find("file://") == 0) { + source_file = source_file.substr(std::strlen("file://")); + } + + source_file_filter = [source_file](auto, auto file_name) { return file_name == source_file; }; } else if (auto url_regex = params.GetValue("urlRegex")) { - source_file_filter = [regex = std::regex(*url_regex)](auto file_name) { + source_file_filter = [regex = std::regex(*url_regex)](auto, auto file_name) { return std::regex_match(file_name.data(), regex); }; } else { @@ -386,27 +366,18 @@ void InspectorServer::OnCallDebuggerSetBreakpointByUrl( return; } - std::set source_files; auto thread = session_manager_->GetThreadBySessionId(session_id); - auto id = handler(thread, source_file_filter, line_number, source_files); - if (!id) { + auto breakpoint = handler(thread, source_file_filter, line_number); + if (!breakpoint) { LOG(INFO, DEBUGGER) << "Failed to set breakpoint"; return; } - result.AddProperty("breakpointId", std::to_string(*id)); - result.AddProperty("locations", [this, line_number, &source_files, thread](JsonArrayBuilder &locations) { - for (auto source_file : source_files) { - locations.Add([this, line_number, thread, source_file](JsonObjectBuilder &location) { - auto [script_id, is_new] = source_manager_.GetScriptId(thread, source_file); - - if (is_new) { - CallDebuggerScriptParsed(thread, script_id, source_file); - } - - Location(script_id, line_number).ToJson()(location); - }); + result.AddProperty("breakpointId", std::to_string(breakpoint->first)); + result.AddProperty("locations", [&](JsonArrayBuilder &locations) { + for (auto &location : breakpoint->second) { + locations.Add(location.ToJson()); } }); }); @@ -429,6 +400,33 @@ void InspectorServer::OnCallDebuggerSetBreakpointsActive(std::function &&handler) +{ + server_.OnCall("Debugger.setPauseOnExceptions", + [this, handler = std::move(handler)](auto &session_id, auto &, const JsonObject ¶ms) { + auto state = params.GetValue("state"); + if (state == nullptr) { + LOG(INFO, DEBUGGER) << "No 'state' property"; + return; + } + + auto thread = session_manager_->GetThreadBySessionId(session_id); + + if (*state == "none") { + handler(thread, ThreadState::PauseOnExceptions::NONE); + } else if (*state == "caught") { + handler(thread, ThreadState::PauseOnExceptions::CAUGHT); + } else if (*state == "uncaught") { + handler(thread, ThreadState::PauseOnExceptions::UNCAUGHT); + } else if (*state == "all") { + handler(thread, ThreadState::PauseOnExceptions::ALL); + } else { + LOG(INFO, DEBUGGER) << "Invalid 'state' value: " << *state; + } + }); +} + void InspectorServer::OnCallDebuggerStepInto(std::function &&handler) { server_.OnCall("Debugger.stepInto", [this, handler = std::move(handler)](auto &session_id, auto &, auto &) { diff --git a/runtime/tooling/inspector/inspector_server.h b/runtime/tooling/inspector/inspector_server.h index 9d4a8cc8ea7e8f87a52207b4ee7a64cecd9d3a19..4673d61d6ed9da494a52709840a8c70efc5b2b27 100644 --- a/runtime/tooling/inspector/inspector_server.h +++ b/runtime/tooling/inspector/inspector_server.h @@ -17,7 +17,8 @@ #define PANDA_TOOLING_INSPECTOR_INSPECTOR_SERVER_H #include "session_manager.h" -#include "source_manager.h" +#include "thread_state.h" +#include "types/location.h" #include "types/numeric_id.h" #include "console_call_type.h" @@ -27,15 +28,17 @@ #include "tooling/pt_thread.h" #include -#include -#include #include +#include #include #include #include +#include #include namespace panda::tooling::inspector { +class CallFrame; +class ExecutionContextDescription; class Server; // NOLINT(fuchsia-virtual-inheritance) class InspectorServer final { @@ -53,32 +56,32 @@ public: void OnOpen(std::function &&handler); void OnFail(std::function &&handler); - void CallDebuggerPaused( - PtThread thread, const std::vector &hit_breakpoints, - const std::function &)> &)> &enumerate_frames); + void CallDebuggerPaused(PtThread thread, const std::vector &hit_breakpoints, + std::optional exception, const std::shared_ptr &call_stack); void CallDebuggerResumed(PtThread thread); void CallDebuggerScriptParsed(PtThread thread, ScriptId id, std::string_view source_file); void CallRuntimeConsoleApiCalled(PtThread thread, ConsoleCallType type, uint64_t timestamp, const std::vector &arguments); - void CallRuntimeExecutionContextCreated(PtThread thread); + void CallRuntimeExecutionContextCreated(PtThread thread, const ExecutionContextDescription &execution_context); + void CallRuntimeExecutionContextDestroyed(PtThread thread, const ExecutionContextDescription &execution_context); void CallTargetAttachedToTarget(PtThread thread); void CallTargetDetachedFromTarget(PtThread thread); - void OnCallDebuggerContinueToLocation(std::function &&handler); + void OnCallDebuggerContinueToLocation(std::function &&handler); void OnCallDebuggerGetPossibleBreakpoints( - std::function(std::string_view, size_t, size_t, bool)> &&handler); - void OnCallDebuggerGetScriptSource(std::function &&handler); + std::function(ScriptId, size_t, size_t, bool)> &&handler); + void OnCallDebuggerGetScriptSource(std::function &&handler); void OnCallDebuggerPause(std::function &&handler); void OnCallDebuggerRemoveBreakpoint(std::function &&handler); void OnCallDebuggerResume(std::function &&handler); void OnCallDebuggerSetBreakpoint( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler); + std::function>>( + PtThread, const std::function &, size_t)> &&handler); void OnCallDebuggerSetBreakpointByUrl( - std::function(PtThread, const std::function &, size_t, - std::set &)> &&handler); + std::function>>( + PtThread, const std::function &, size_t)> &&handler); void OnCallDebuggerSetBreakpointsActive(std::function &&handler); + void OnCallDebuggerSetPauseOnExceptions(std::function &&handler); void OnCallDebuggerStepInto(std::function &&handler); void OnCallDebuggerStepOut(std::function &&handler); void OnCallDebuggerStepOver(std::function &&handler); @@ -92,7 +95,6 @@ private: Server &server_; std::unique_ptr session_manager_; - SourceManager source_manager_; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/tests/inspector_test.cpp b/runtime/tooling/inspector/tests/inspector_test.cpp index a56f77ab06f8dd6a05db64a3e4f83d85e3e02b69..1e46a7371219e5b8f24bfac759edb7596e62d7d0 100644 --- a/runtime/tooling/inspector/tests/inspector_test.cpp +++ b/runtime/tooling/inspector/tests/inspector_test.cpp @@ -102,8 +102,8 @@ private: TEST_F(InspectorTest, InitialSequence) { - ExpectUnsupportedMethods("Profiler.enable", "Debugger.setPauseOnExceptions", "Debugger.setAsyncCallStackDepth", - "Runtime.getIsolateId", "Debugger.setBlackboxPatterns"); + ExpectUnsupportedMethods("Profiler.enable", "Debugger.setAsyncCallStackDepth", "Runtime.getIsolateId", + "Debugger.setBlackboxPatterns"); // NOLINTNEXTLINE(readability-isolate-declaration) MockFunction execution_context_created, runtime_enabled, debugger_enabled, debugger_paused, @@ -116,6 +116,7 @@ TEST_F(InspectorTest, InitialSequence) EXPECT_CALL(debugger_paused, Call); EXPECT_CALL(debugger_resumed, Call); + debugger_.GetHooks().VmInitialization(PtThread::NONE); debugger_.GetHooks().ThreadStart(PtThread::NONE); Call("Profiler.enable", Callback()); @@ -159,6 +160,7 @@ TEST_F(InspectorTest, BreaksOnStart) EXPECT_CALL(debugger_resumed, Call); EXPECT_CALL(debugger_resume_replied_to, Call); + debugger_.GetHooks().VmInitialization(PtThread::NONE); debugger_.GetHooks().ThreadStart(PtThread::NONE); Call("Runtime.runIfWaitingForDebugger"); (server_ + client_).Poll(); diff --git a/runtime/tooling/inspector/tests/inspector_test_base.cpp b/runtime/tooling/inspector/tests/inspector_test_base.cpp index e971f973c6ec929497d398142f7c51c625a7cb8c..d5e8338d9123f064a28c1e82ac25e9b7a76fdce6 100644 --- a/runtime/tooling/inspector/tests/inspector_test_base.cpp +++ b/runtime/tooling/inspector/tests/inspector_test_base.cpp @@ -85,6 +85,7 @@ void InspectorTestBase::SetUp() SetUpSourceFiles(); if (AttachDebugger()) { + debugger_.GetHooks().VmInitialization(PtThread::NONE); debugger_.GetHooks().ThreadStart(PtThread::NONE); client_.Call("Runtime.runIfWaitingForDebugger"); (server_ + client_).Poll(); diff --git a/runtime/tooling/inspector/thread_state.cpp b/runtime/tooling/inspector/thread_state.cpp index 15c7047b462a6be907235a578aa1ae9a7cf5d804..c62e79cd61296b3691103275a4fa2011a116ac54 100644 --- a/runtime/tooling/inspector/thread_state.cpp +++ b/runtime/tooling/inspector/thread_state.cpp @@ -15,10 +15,23 @@ #include "thread_state.h" +#include "types/numeric_id.h" + +#include "macros.h" +#include "os/mutex.h" + +#include +#include +#include +#include + namespace panda::tooling::inspector { -ThreadState::ThreadState(std::function &)> &&suspend, - std::function &&wait_suspension, std::function &&resume) - : suspend_(std::move(suspend)), wait_suspension_(std::move(wait_suspension)), resume_(std::move(resume)) +ThreadState::ThreadState(GetCallStackFunction &&get_call_stack, SuspendFunction &&suspend, + WaitSuspensionFunction &&wait_suspension, ResumeFunction &&resume) + : get_call_stack_(std::move(get_call_stack)), + suspend_(std::move(suspend)), + wait_suspension_(std::move(wait_suspension)), + resume_(std::move(resume)) { } @@ -33,6 +46,9 @@ void ThreadState::Reset() breakpoints_active_ = true; next_breakpoint_id_ = 0; breakpoint_locations_.clear(); + pause_on_exceptions_ = PauseOnExceptions::NONE; + exception_.reset(); + exception_call_stack_.reset(); } void ThreadState::BreakOnStart() @@ -137,6 +153,57 @@ void ThreadState::RemoveBreakpoint(BreakpointId id) } } +void ThreadState::SetPauseOnExceptions(PauseOnExceptions state) +{ + os::memory::LockHolder lock(mutex_); + pause_on_exceptions_ = state; +} + +void ThreadState::OnException(const PtLocation &location, RemoteObject object) +{ + os::memory::LockHolder lock(mutex_); + + if (pause_on_exceptions_ == PauseOnExceptions::NONE) { + return; + } + + // If there is an active exception, it has just become uncaught + if (exception_ && pause_on_exceptions_ == PauseOnExceptions::UNCAUGHT) { + paused_ = true; + WaitForUnpause(&location); + } + + exception_ = std::move(object); + exception_call_stack_ = get_call_stack_(); + + if (pause_on_exceptions_ == PauseOnExceptions::ALL) { + paused_ = true; + WaitForUnpause(&location); + } +} + +void ThreadState::OnExceptionCatch(const PtLocation &location) +{ + os::memory::LockHolder lock(mutex_); + + switch (pause_on_exceptions_) { + case PauseOnExceptions::NONE: + case PauseOnExceptions::ALL: + return; + + case PauseOnExceptions::CAUGHT: + ASSERT(exception_); + paused_ = true; + WaitForUnpause(&location); + break; + + case PauseOnExceptions::UNCAUGHT: + exception_.reset(); + exception_call_stack_.reset(); + break; + } +} + void ThreadState::OnFramePop() { os::memory::LockHolder lock(mutex_); @@ -225,13 +292,34 @@ void ThreadState::OnSingleStep(const PtLocation &location) paused_ = true; } + WaitForUnpause(&location); +} + +void ThreadState::OnThreadEnd() +{ + os::memory::LockHolder lock(mutex_); + + if (exception_ && pause_on_exceptions_ == PauseOnExceptions::UNCAUGHT) { + paused_ = true; + WaitForUnpause(nullptr); + } +} + +void ThreadState::WaitForUnpause(const PtLocation *location) +{ while (paused_) { + auto call_stack = exception_ ? std::move(exception_call_stack_) : get_call_stack_(); + std::vector hit_breakpoints; - auto range = breakpoint_locations_.equal_range(location); - std::transform(range.first, range.second, std::back_inserter(hit_breakpoints), - [](auto &p) { return p.second; }); - suspend_(hit_breakpoints); + if (location != nullptr) { + auto range = breakpoint_locations_.equal_range(*location); + std::transform(range.first, range.second, std::back_inserter(hit_breakpoints), + [](auto &p) { return p.second; }); + } + + suspend_(call_stack, hit_breakpoints, std::move(exception_)); + exception_.reset(); mutex_.Unlock(); wait_suspension_(); diff --git a/runtime/tooling/inspector/thread_state.h b/runtime/tooling/inspector/thread_state.h index 78c8c8dac0ed9d46254d0c6f341ed0dae314ebee..00f927af467f52f0797928c759d8b9ca84c5dbd7 100644 --- a/runtime/tooling/inspector/thread_state.h +++ b/runtime/tooling/inspector/thread_state.h @@ -18,12 +18,20 @@ #include "types/numeric_id.h" +#include "os/mutex.h" #include "tooling/debugger.h" +#include "tooling/inspector/remote_object.h" +#include "tooling/pt_location.h" +#include +#include +#include #include #include namespace panda::tooling::inspector { +class CallFrame; + class ThreadState final { public: enum class StepKind { @@ -51,8 +59,16 @@ public: STEP_OVER }; - ThreadState(std::function &)> &&suspend, - std::function &&wait_suspension, std::function &&resume); + enum class PauseOnExceptions { NONE, CAUGHT, UNCAUGHT, ALL }; + + using GetCallStackFunction = std::function()>; + using SuspendFunction = std::function &, const std::vector &, + std::optional)>; + using WaitSuspensionFunction = std::function; + using ResumeFunction = std::function; + + ThreadState(GetCallStackFunction &&get_call_stack, SuspendFunction &&suspend, + WaitSuspensionFunction &&wait_suspension, ResumeFunction &&resume); ~ThreadState() = default; NO_COPY_SEMANTIC(ThreadState); @@ -98,12 +114,21 @@ public: // Removes the breakpoint by ID void RemoveBreakpoint(BreakpointId id); + // Sets the pause-on-exceptions state + void SetPauseOnExceptions(PauseOnExceptions state); + //////////////////////////////////////////////////////////////////////////// // // The following methods should be called on an application thread // //////////////////////////////////////////////////////////////////////////// + // Notification that an exception was created + void OnException(const PtLocation &location, RemoteObject object); + + // Notification that an exception was caught + void OnExceptionCatch(const PtLocation &location); + // Notification that an "interesting" frame was popped void OnFramePop(); @@ -114,17 +139,26 @@ public: // Notification that a next step will be performed. Pauses the thread if necessary void OnSingleStep(const PtLocation &location); + // Notification that the thread ended + void OnThreadEnd(); + private: + // Suspends the thread until it is unpaused + void WaitForUnpause(const PtLocation *location); + os::memory::Mutex mutex_; + // Gets the current call stack. Should be called on an application thread + GetCallStackFunction get_call_stack_ GUARDED_BY(mutex_); + // Marks a paused thread as suspended. Should be called on an application thread - std::function &)> suspend_ GUARDED_BY(mutex_); + SuspendFunction suspend_ GUARDED_BY(mutex_); // Waits until the suspension of a paused thread ends. Should be called on an application thread - std::function wait_suspension_; + WaitSuspensionFunction wait_suspension_; // Marks a paused thread as not suspended. Should be called on the server thread - std::function resume_ GUARDED_BY(mutex_); + ResumeFunction resume_ GUARDED_BY(mutex_); StepKind step_kind_ GUARDED_BY(mutex_) {StepKind::NONE}; @@ -140,6 +174,10 @@ private: BreakpointId next_breakpoint_id_ GUARDED_BY(mutex_) {0}; std::unordered_multimap breakpoint_locations_ GUARDED_BY(mutex_); + PauseOnExceptions pause_on_exceptions_ GUARDED_BY(mutex_) {PauseOnExceptions::NONE}; + std::optional exception_ GUARDED_BY(mutex_); + std::shared_ptr exception_call_stack_ GUARDED_BY(mutex_); + bool paused_ GUARDED_BY(mutex_) {false}; }; } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/call_frame.cpp b/runtime/tooling/inspector/types/call_frame.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6ef0a7abcd1eb2f3df87b494ea9d4f21e65f7319 --- /dev/null +++ b/runtime/tooling/inspector/types/call_frame.cpp @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2022 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. + */ + +#include "call_frame.h" + +#include "utils/json_builder.h" + +#include + +namespace panda::tooling::inspector { +std::function CallFrame::ToJson() const +{ + return [this](auto &json_builder) { + json_builder.AddProperty("callFrameId", std::to_string(id_)); + json_builder.AddProperty("functionName", function_name_); + json_builder.AddProperty("location", location_.ToJson()); + json_builder.AddProperty("url", url_); + + json_builder.AddProperty("scopeChain", [&](JsonArrayBuilder &scope_chain_builder) { + for (auto scope = scope_chain_; scope; scope = scope->GetNext()) { + scope_chain_builder.Add(scope->ToJson()); + } + }); + }; +} +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/call_frame.h b/runtime/tooling/inspector/types/call_frame.h new file mode 100644 index 0000000000000000000000000000000000000000..36a1e2d744e1265c43b037e7ca77480cd818ce0e --- /dev/null +++ b/runtime/tooling/inspector/types/call_frame.h @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2022 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 PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H +#define PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H + +#include "location.h" +#include "numeric_id.h" +#include "scope.h" + +#include +#include +#include +#include + +namespace panda { +class JsonObjectBuilder; +} // namespace panda + +namespace panda::tooling::inspector { +class CallFrame { +public: + CallFrame(CallFrameId id, std::string_view function_name, Location location, std::string_view url, + std::shared_ptr scope_chain, std::shared_ptr next) + : id_(id), + function_name_(function_name), + location_(location), + url_(url), + scope_chain_(std::move(scope_chain)), + next_(std::move(next)) + { + } + + std::shared_ptr GetNext() const + { + return next_; + } + + std::function ToJson() const; + +private: + CallFrameId id_; + std::string_view function_name_; + Location location_; + std::string_view url_; + std::shared_ptr scope_chain_; + std::shared_ptr next_; +}; +} // namespace panda::tooling::inspector + +#endif // PANDA_TOOLING_INSPECTOR_TYPES_CALL_FRAME_H diff --git a/runtime/tooling/inspector/types/execution_context_description.cpp b/runtime/tooling/inspector/types/execution_context_description.cpp new file mode 100644 index 0000000000000000000000000000000000000000..52397dec2b2754b46fd89b45ee7e5fc6a311185a --- /dev/null +++ b/runtime/tooling/inspector/types/execution_context_description.cpp @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2022 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. + */ + +#include "execution_context_description.h" + +#include "tooling/pt_thread.h" +#include "utils/json_builder.h" + +#include + +namespace panda::tooling::inspector { +ExecutionContextDescription::ExecutionContextDescription(PtThread thread) : thread_(thread) +{ + if (thread != PtThread::NONE) { + name_ = "Thread #" + std::to_string(thread.GetId()); + } +} + +std::function ExecutionContextDescription::ToJson() const +{ + return [this](JsonObjectBuilder &json_builder) { + json_builder.AddProperty("id", GetId()); + json_builder.AddProperty("origin", ""); + json_builder.AddProperty("name", name_); + }; +} +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/execution_context_description.h b/runtime/tooling/inspector/types/execution_context_description.h new file mode 100644 index 0000000000000000000000000000000000000000..34da47555d140e1cbadfae17e32192f46c06b5be --- /dev/null +++ b/runtime/tooling/inspector/types/execution_context_description.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2022 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 PANDA_TOOLING_INSPECTOR_TYPES_EXECUTION_CONTEXT_DESCRIPTION_H +#define PANDA_TOOLING_INSPECTOR_TYPES_EXECUTION_CONTEXT_DESCRIPTION_H + +#include "numeric_id.h" + +#include "tooling/pt_thread.h" + +#include +#include + +namespace panda { +class JsonObjectBuilder; +} // namespace panda + +namespace panda::tooling::inspector { +class ExecutionContextDescription { +public: + explicit ExecutionContextDescription(PtThread thread); + + ExecutionContextId GetId() const + { + return thread_.GetId(); + } + + std::function ToJson() const; + +private: + PtThread thread_; + std::string name_; +}; +} // namespace panda::tooling::inspector + +#endif // PANDA_TOOLING_INSPECTOR_TYPES_EXECUTION_CONTEXT_DESCRIPTION_H diff --git a/runtime/tooling/inspector/types/numeric_id.h b/runtime/tooling/inspector/types/numeric_id.h index 6dc4826e7a9da92bbff2b47283c5fe5aa39d039b..0f6e8f4c3512061588fdefd51e85244f928433c3 100644 --- a/runtime/tooling/inspector/types/numeric_id.h +++ b/runtime/tooling/inspector/types/numeric_id.h @@ -28,6 +28,8 @@ namespace panda::tooling::inspector { using BreakpointId = size_t; +using CallFrameId = uint32_t; +using ExecutionContextId = uint32_t; using FrameId = uint32_t; using ScriptId = size_t; diff --git a/runtime/tooling/inspector/types/scope.cpp b/runtime/tooling/inspector/types/scope.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4afcb9cd91536726940e375960e0e555be0e3189 --- /dev/null +++ b/runtime/tooling/inspector/types/scope.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2022 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. + */ + +#include "scope.h" + +#include "utils/json_builder.h" + +namespace panda::tooling::inspector { +std::function Scope::ToJson() const +{ + return [this](auto &json_builder) { + json_builder.AddProperty("type", "local"); + json_builder.AddProperty("object", object_.ToJson()); + }; +} +} // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/types/scope.h b/runtime/tooling/inspector/types/scope.h new file mode 100644 index 0000000000000000000000000000000000000000..3cc2ac63849ae31aa3e83e827ebe90827c8d53b7 --- /dev/null +++ b/runtime/tooling/inspector/types/scope.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2022 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 PANDA_TOOLING_INSPECTOR_TYPES_SCOPE_H +#define PANDA_TOOLING_INSPECTOR_TYPES_SCOPE_H + +#include "tooling/inspector/remote_object.h" + +#include +#include +#include + +namespace panda { +class JsonObjectBuilder; +} // namespace panda + +namespace panda::tooling::inspector { +class Scope { +public: + explicit Scope(RemoteObject object, std::shared_ptr next = nullptr) + : object_(std::move(object)), next_(std::move(next)) + { + } + + std::shared_ptr GetNext() const + { + return next_; + } + + std::function ToJson() const; + +private: + RemoteObject object_; + std::shared_ptr next_; +}; +} // namespace panda::tooling::inspector + +#endif // PANDA_TOOLING_INSPECTOR_TYPES_SCOPE_H