From 7599b0ef9dcd28dd47e0c876cf51bf65fa15c73d Mon Sep 17 00:00:00 2001 From: Eugene Ostroukhov Date: Mon, 12 Dec 2016 17:08:31 -0800 Subject: [PATCH] debug: activate inspector with _debugProcess This pull request switches the signal handler to start inspector socket server instead of the legacy V8 debug protocol. PR-URL: https://github.com/nodejs/node/pull/11431 Fixes: https://github.com/nodejs/node/issues/8464 Reviewed-By: Ben Noordhuis --- node.gypi | 2 + src/inspector_agent.cc | 924 +++++++++++++++----------------------- src/inspector_agent.h | 18 +- src/inspector_io.cc | 441 ++++++++++++++++++ src/inspector_io.h | 125 ++++++ src/node.cc | 153 +------ src/node_debug_options.cc | 6 +- 7 files changed, 973 insertions(+), 696 deletions(-) create mode 100644 src/inspector_io.cc create mode 100644 src/inspector_io.h diff --git a/node.gypi b/node.gypi index d78d24da8b39cd..ac05072bc835f6 100644 --- a/node.gypi +++ b/node.gypi @@ -77,9 +77,11 @@ ], 'sources': [ 'src/inspector_agent.cc', + 'src/inspector_io.cc', 'src/inspector_socket.cc', 'src/inspector_socket_server.cc', 'src/inspector_agent.h', + 'src/inspector_io.h', 'src/inspector_socket.h', 'src/inspector_socket_server.h', ], diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 34ba5a7fc9d4be..dac4c10495478f 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -1,12 +1,9 @@ #include "inspector_agent.h" -#include "inspector_socket_server.h" +#include "inspector_io.h" #include "env.h" #include "env-inl.h" #include "node.h" -#include "node_crypto.h" -#include "node_mutex.h" -#include "node_version.h" #include "v8-inspector.h" #include "v8-platform.h" #include "util.h" @@ -14,142 +11,173 @@ #include "libplatform/libplatform.h" -#include -#include -#include -#include - #include -#include #include +#ifdef __POSIX__ +#include // setuid, getuid +#endif // __POSIX__ namespace node { namespace inspector { namespace { +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; using v8_inspector::StringBuffer; using v8_inspector::StringView; +using v8_inspector::V8Inspector; + +static uv_sem_t inspector_io_thread_semaphore; +static uv_async_t start_inspector_thread_async; + +std::unique_ptr ToProtocolString(Isolate* isolate, + Local value) { + TwoByteValue buffer(isolate, value); + return StringBuffer::create(StringView(*buffer, buffer.length())); +} + +#ifdef __POSIX__ +static void EnableInspectorIOThreadSignalHandler(int signo) { + uv_sem_post(&inspector_io_thread_semaphore); +} + +inline void* InspectorIoThreadSignalThreadMain(void* unused) { + for (;;) { + uv_sem_wait(&inspector_io_thread_semaphore); + uv_async_send(&start_inspector_thread_async); + } + return nullptr; +} + +static int RegisterDebugSignalHandler() { + // Start a watchdog thread for calling v8::Debug::DebugBreak() because + // it's not safe to call directly from the signal handler, it can + // deadlock with the thread it interrupts. + CHECK_EQ(0, uv_sem_init(&inspector_io_thread_semaphore, 0)); + pthread_attr_t attr; + CHECK_EQ(0, pthread_attr_init(&attr)); + // Don't shrink the thread's stack on FreeBSD. Said platform decided to + // follow the pthreads specification to the letter rather than in spirit: + // https://lists.freebsd.org/pipermail/freebsd-current/2014-March/048885.html +#ifndef __FreeBSD__ + CHECK_EQ(0, pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN)); +#endif // __FreeBSD__ + CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + sigset_t sigmask; + sigfillset(&sigmask); + CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask)); + pthread_t thread; + const int err = pthread_create(&thread, &attr, + InspectorIoThreadSignalThreadMain, nullptr); + CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr)); + CHECK_EQ(0, pthread_attr_destroy(&attr)); + if (err != 0) { + fprintf(stderr, "node[%d]: pthread_create: %s\n", getpid(), strerror(err)); + fflush(stderr); + // Leave SIGUSR1 blocked. We don't install a signal handler, + // receiving the signal would terminate the process. + return -err; + } + RegisterSignalHandler(SIGUSR1, EnableInspectorIOThreadSignalHandler); + // Unblock SIGUSR1. A pending SIGUSR1 signal will now be delivered. + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr)); + return 0; +} +#endif // __POSIX__ -std::string GetProcessTitle() { - // uv_get_process_title will trim the title if it is too long. - char title[2048]; - int err = uv_get_process_title(title, sizeof(title)); - if (err == 0) { - return title; - } else { - return "Node.js"; - } -} - -// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt -// Used ver 4 - with numbers -std::string GenerateID() { - uint16_t buffer[8]; - CHECK(crypto::EntropySource(reinterpret_cast(buffer), - sizeof(buffer))); - - char uuid[256]; - snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", - buffer[0], // time_low - buffer[1], // time_mid - buffer[2], // time_low - (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version - (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low - buffer[5], // node - buffer[6], - buffer[7]); - return uuid; -} - -std::string StringViewToUtf8(const StringView& view) { - if (view.is8Bit()) { - return std::string(reinterpret_cast(view.characters8()), - view.length()); - } - const uint16_t* source = view.characters16(); - const UChar* unicodeSource = reinterpret_cast(source); - static_assert(sizeof(*source) == sizeof(*unicodeSource), - "sizeof(*source) == sizeof(*unicodeSource)"); - - size_t result_length = view.length() * sizeof(*source); - std::string result(result_length, '\0'); - UnicodeString utf16(unicodeSource, view.length()); - // ICU components for std::string compatibility are not enabled in build... - bool done = false; - while (!done) { - CheckedArrayByteSink sink(&result[0], result_length); - utf16.toUTF8(sink); - result_length = sink.NumberOfBytesAppended(); - result.resize(result_length); - done = !sink.Overflowed(); - } - return result; -} - -std::unique_ptr Utf8ToStringView(const std::string& message) { - UnicodeString utf16 = - UnicodeString::fromUTF8(StringPiece(message.data(), message.length())); - StringView view(reinterpret_cast(utf16.getBuffer()), - utf16.length()); - return StringBuffer::create(view); + +#ifdef _WIN32 +DWORD WINAPI EnableDebugThreadProc(void* arg) { + uv_async_send(&start_inspector_thread_async); + return 0; } -} // namespace -class V8NodeInspector; +static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf, + size_t buf_len) { + return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); +} -enum class InspectorAction { - kStartSession, kEndSession, kSendMessage -}; +static int RegisterDebugSignalHandler() { + wchar_t mapping_name[32]; + HANDLE mapping_handle; + DWORD pid; + LPTHREAD_START_ROUTINE* handler; -enum class TransportAction { - kSendMessage, kStop -}; + pid = GetCurrentProcessId(); -class InspectorAgentDelegate: public node::inspector::SocketServerDelegate { - public: - InspectorAgentDelegate(AgentImpl* agent, const std::string& script_path, - const std::string& script_name, bool wait); - bool StartSession(int session_id, const std::string& target_id) override; - void MessageReceived(int session_id, const std::string& message) override; - void EndSession(int session_id) override; - std::vector GetTargetIds() override; - std::string GetTargetTitle(const std::string& id) override; - std::string GetTargetUrl(const std::string& id) override; - bool IsConnected() { return connected_; } - private: - AgentImpl* agent_; - bool connected_; - int session_id_; - const std::string script_name_; - const std::string script_path_; - const std::string target_id_; - bool waiting_; -}; + if (GetDebugSignalHandlerMappingName(pid, + mapping_name, + arraysize(mapping_name)) < 0) { + return -1; + } + + mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE, + 0, + sizeof *handler, + mapping_name); + if (mapping_handle == nullptr) { + return -1; + } + + handler = reinterpret_cast( + MapViewOfFile(mapping_handle, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof *handler)); + if (handler == nullptr) { + CloseHandle(mapping_handle); + return -1; + } + + *handler = EnableDebugThreadProc; + + UnmapViewOfFile(static_cast(handler)); + + return 0; +} +#endif // _WIN32 +} // namespace + + +// Used in NodeInspectorClient::currentTimeMS() below. +const int NANOS_PER_MSEC = 1000000; +const int CONTEXT_GROUP_ID = 1; + +class NodeInspectorClient; class AgentImpl { public: explicit AgentImpl(node::Environment* env); - // Start the inspector agent thread bool Start(v8::Platform* platform, const char* path, const DebugOptions& options); - // Stop the inspector agent void Stop(); - bool IsStarted(); bool IsConnected(); void WaitForDisconnect(); - void FatalException(v8::Local error, - v8::Local message); + void FatalException(Local error, + Local message); void SchedulePauseOnNextStatement(const std::string& reason); - void PostIncomingMessage(InspectorAction action, int session_id, - const std::string& message); - void ResumeStartup() { - uv_sem_post(&start_sem_); + void Connect(InspectorSessionDelegate* session); + + NodeInspectorClient* client() { + return inspector_.get(); } private: @@ -160,82 +188,56 @@ class AgentImpl { static void ThreadCbIO(void* agent); static void WriteCbIO(uv_async_t* async); - static void MainThreadAsyncCb(uv_async_t* req); - static void CallAndPauseOnStart( - const v8::FunctionCallbackInfo& args); - - void InstallInspectorOnProcess(); + static void CallAndPauseOnStart(const v8::FunctionCallbackInfo&); void WorkerRunIO(); void SetConnected(bool connected); - void DispatchMessages(); - void Write(TransportAction action, int session_id, const StringView& message); - template - bool AppendMessage(MessageQueue* vector, ActionType action, - int session_id, std::unique_ptr buffer); - template - void SwapBehindLock(MessageQueue* vector1, - MessageQueue* vector2); void WaitForFrontendMessage(); void NotifyMessageReceived(); + bool StartIoThread(); + static void InspectorWrapConsoleCall( + const v8::FunctionCallbackInfo& args); + static void InspectorConsoleCall( + const v8::FunctionCallbackInfo& info); + static void StartInspectorIoThreadAsyncCallback(uv_async_t* handle); State ToState(State state); - DebugOptions options_; - uv_sem_t start_sem_; - ConditionVariable incoming_message_cond_; - Mutex state_lock_; - uv_thread_t thread_; - uv_loop_t child_loop_; - - InspectorAgentDelegate* delegate_; - bool wait_; - bool shutting_down_; - State state_; node::Environment* parent_env_; - - uv_async_t io_thread_req_; - uv_async_t main_thread_req_; - V8NodeInspector* inspector_; + std::unique_ptr inspector_; + std::unique_ptr io_; v8::Platform* platform_; - MessageQueue incoming_message_queue_; - MessageQueue outgoing_message_queue_; - bool dispatching_messages_; - int session_id_; - InspectorSocketServer* server_; - - std::string script_name_; - std::string script_path_; - const std::string id_; - - friend class ChannelImpl; - friend class DispatchOnInspectorBackendTask; - friend class SetConnectedTask; - friend class V8NodeInspector; - friend void InterruptCallback(v8::Isolate*, void* agent); - friend void DataCallback(uv_stream_t* stream, ssize_t read, - const uv_buf_t* buf); + bool inspector_console_; + std::string path_; + DebugOptions debug_options_; }; -void InterruptCallback(v8::Isolate*, void* agent) { - static_cast(agent)->DispatchMessages(); -} - -class DispatchOnInspectorBackendTask : public v8::Task { +class ChannelImpl final : public v8_inspector::V8Inspector::Channel { public: - explicit DispatchOnInspectorBackendTask(AgentImpl* agent) : agent_(agent) {} + explicit ChannelImpl(V8Inspector* inspector, + InspectorSessionDelegate* delegate) + : delegate_(delegate) { + session_ = inspector->connect(1, this, StringView()); + } + + virtual ~ChannelImpl() {} - void Run() override { - agent_->DispatchMessages(); + void dispatchProtocolMessage(const StringView& message) { + session_->dispatchProtocolMessage(message); } - private: - AgentImpl* agent_; -}; + bool waitForFrontendMessage() { + return delegate_->WaitForFrontendMessage(); + } + + void schedulePauseOnNextStatement(const std::string& reason) { + std::unique_ptr buffer = Utf8ToStringView(reason); + session_->schedulePauseOnNextStatement(buffer->string(), buffer->string()); + } + + InspectorSessionDelegate* delegate() { + return delegate_; + } -class ChannelImpl final : public v8_inspector::V8Inspector::Channel { - public: - explicit ChannelImpl(AgentImpl* agent): agent_(agent) {} - virtual ~ChannelImpl() {} private: void sendResponse( int callId, @@ -251,40 +253,35 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel { void flushProtocolNotifications() override { } void sendMessageToFrontend(const StringView& message) { - agent_->Write(TransportAction::kSendMessage, agent_->session_id_, message); + delegate_->OnMessage(message); } - AgentImpl* const agent_; + InspectorSessionDelegate* const delegate_; + std::unique_ptr session_; }; -// Used in V8NodeInspector::currentTimeMS() below. -#define NANOS_PER_MSEC 1000000 - -using V8Inspector = v8_inspector::V8Inspector; - -class V8NodeInspector : public v8_inspector::V8InspectorClient { +class NodeInspectorClient : public v8_inspector::V8InspectorClient { public: - V8NodeInspector(AgentImpl* agent, node::Environment* env, - v8::Platform* platform) - : agent_(agent), - env_(env), - platform_(platform), - terminated_(false), - running_nested_loop_(false), - inspector_(V8Inspector::create(env->isolate(), this)) { + NodeInspectorClient(node::Environment* env, + v8::Platform* platform) : env_(env), + platform_(platform), + terminated_(false), + running_nested_loop_(false) { + inspector_ = V8Inspector::create(env->isolate(), this); const uint8_t CONTEXT_NAME[] = "Node.js Main Context"; StringView context_name(CONTEXT_NAME, sizeof(CONTEXT_NAME) - 1); - v8_inspector::V8ContextInfo info(env->context(), 1, context_name); + v8_inspector::V8ContextInfo info(env->context(), CONTEXT_GROUP_ID, + context_name); inspector_->contextCreated(info); } void runMessageLoopOnPause(int context_group_id) override { + CHECK_NE(channel_, nullptr); if (running_nested_loop_) return; terminated_ = false; running_nested_loop_ = true; - while (!terminated_) { - agent_->WaitForFrontendMessage(); + while (!terminated_ && channel_->waitForFrontendMessage()) { while (v8::platform::PumpMessageLoop(platform_, env_->isolate())) {} } @@ -300,111 +297,128 @@ class V8NodeInspector : public v8_inspector::V8InspectorClient { terminated_ = true; } - void connectFrontend() { - session_ = inspector_->connect(1, new ChannelImpl(agent_), StringView()); + void connectFrontend(InspectorSessionDelegate* delegate) { + CHECK_EQ(channel_, nullptr); + channel_ = std::unique_ptr( + new ChannelImpl(inspector_.get(), delegate)); } void disconnectFrontend() { - session_.reset(); + quitMessageLoopOnPause(); + channel_.reset(); } void dispatchMessageFromFrontend(const StringView& message) { - CHECK(session_); - session_->dispatchProtocolMessage(message); + CHECK_NE(channel_, nullptr); + channel_->dispatchProtocolMessage(message); } void schedulePauseOnNextStatement(const std::string& reason) { - if (session_ != nullptr) { - std::unique_ptr buffer = Utf8ToStringView(reason); - session_->schedulePauseOnNextStatement(buffer->string(), - buffer->string()); + if (channel_ != nullptr) { + channel_->schedulePauseOnNextStatement(reason); } } - v8::Local ensureDefaultContextInGroup(int contextGroupId) - override { + Local ensureDefaultContextInGroup(int contextGroupId) override { return env_->context(); } - V8Inspector* inspector() { - return inspector_.get(); + void FatalException(Local error, Local message) { + Local context = env_->context(); + + int script_id = message->GetScriptOrigin().ScriptID()->Value(); + + Local stack_trace = message->GetStackTrace(); + + if (!stack_trace.IsEmpty() && + stack_trace->GetFrameCount() > 0 && + script_id == stack_trace->GetFrame(0)->GetScriptId()) { + script_id = 0; + } + + const uint8_t DETAILS[] = "Uncaught"; + + Isolate* isolate = context->GetIsolate(); + + inspector_->exceptionThrown( + context, + StringView(DETAILS, sizeof(DETAILS) - 1), + error, + ToProtocolString(isolate, message->Get())->string(), + ToProtocolString(isolate, message->GetScriptResourceName())->string(), + message->GetLineNumber(context).FromMaybe(0), + message->GetStartColumn(context).FromMaybe(0), + inspector_->createStackTrace(stack_trace), + script_id); + } + + InspectorSessionDelegate* delegate() { + if (channel_ == nullptr) + return nullptr; + return channel_->delegate(); } private: - AgentImpl* agent_; node::Environment* env_; v8::Platform* platform_; bool terminated_; bool running_nested_loop_; std::unique_ptr inspector_; - std::unique_ptr session_; + std::unique_ptr channel_; }; -AgentImpl::AgentImpl(Environment* env) : delegate_(nullptr), - wait_(false), - shutting_down_(false), - state_(State::kNew), - parent_env_(env), +AgentImpl::AgentImpl(Environment* env) : parent_env_(env), inspector_(nullptr), platform_(nullptr), - dispatching_messages_(false), - session_id_(0), - server_(nullptr) { - CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_, - AgentImpl::MainThreadAsyncCb)); - uv_unref(reinterpret_cast(&main_thread_req_)); - CHECK_EQ(0, uv_sem_init(&start_sem_, 0)); - memset(&io_thread_req_, 0, sizeof(io_thread_req_)); -} + inspector_console_(false) {} -void InspectorConsoleCall(const v8::FunctionCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::Local context = isolate->GetCurrentContext(); +// static +void AgentImpl::InspectorConsoleCall( + const v8::FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); CHECK(info.Data()->IsArray()); - v8::Local args = info.Data().As(); + Local args = info.Data().As(); CHECK_EQ(args->Length(), 3); - v8::Local inspector_method = - args->Get(context, 0).ToLocalChecked(); - CHECK(inspector_method->IsFunction()); - v8::Local node_method = - args->Get(context, 1).ToLocalChecked(); - CHECK(node_method->IsFunction()); - v8::Local config_value = - args->Get(context, 2).ToLocalChecked(); - CHECK(config_value->IsObject()); - v8::Local config_object = config_value.As(); - - std::vector> call_args(info.Length()); + std::vector> call_args(info.Length()); for (int i = 0; i < info.Length(); ++i) { call_args[i] = info[i]; } - v8::Local in_call_key = OneByteString(isolate, "in_call"); - bool in_call = config_object->Has(context, in_call_key).FromMaybe(false); - if (!in_call) { - CHECK(config_object->Set(context, - in_call_key, - v8::True(isolate)).FromJust()); - CHECK(!inspector_method.As()->Call( - context, - info.Holder(), - call_args.size(), - call_args.data()).IsEmpty()); + Environment* env = Environment::GetCurrent(isolate); + if (env->inspector_agent()->impl->inspector_console_) { + Local inspector_method = args->Get(context, 0).ToLocalChecked(); + CHECK(inspector_method->IsFunction()); + Local config_value = args->Get(context, 2).ToLocalChecked(); + CHECK(config_value->IsObject()); + Local config_object = config_value.As(); + Local in_call_key = FIXED_ONE_BYTE_STRING(isolate, "in_call"); + if (!config_object->Has(context, in_call_key).FromMaybe(false)) { + CHECK(config_object->Set(context, + in_call_key, + v8::True(isolate)).FromJust()); + CHECK(!inspector_method.As()->Call(context, + info.Holder(), + call_args.size(), + call_args.data()).IsEmpty()); + } + CHECK(config_object->Delete(context, in_call_key).FromJust()); } - v8::TryCatch try_catch(info.GetIsolate()); - static_cast(node_method.As()->Call(context, - info.Holder(), - call_args.size(), - call_args.data())); - CHECK(config_object->Delete(context, in_call_key).FromJust()); - if (try_catch.HasCaught()) - try_catch.ReThrow(); + Local node_method = + args->Get(context, 1).ToLocalChecked(); + CHECK(node_method->IsFunction()); + static_cast(node_method.As()->Call(context, + info.Holder(), + call_args.size(), + call_args.data())); } -void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) { +// static +void AgentImpl::InspectorWrapConsoleCall( + const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (args.Length() != 3 || !args[0]->IsFunction() || @@ -413,84 +427,112 @@ void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) { "arguments: two functions and an object."); } - v8::Local array = v8::Array::New(env->isolate(), args.Length()); + Local array = v8::Array::New(env->isolate(), args.Length()); CHECK(array->Set(env->context(), 0, args[0]).FromJust()); CHECK(array->Set(env->context(), 1, args[1]).FromJust()); CHECK(array->Set(env->context(), 2, args[2]).FromJust()); - args.GetReturnValue().Set(v8::Function::New(env->context(), - InspectorConsoleCall, - array).ToLocalChecked()); + args.GetReturnValue().Set(Function::New(env->context(), + InspectorConsoleCall, + array).ToLocalChecked()); +} + +// Called from the main thread. +// static +void AgentImpl::StartInspectorIoThreadAsyncCallback(uv_async_t* handle) { + reinterpret_cast(handle->data)->StartIoThread(); } bool AgentImpl::Start(v8::Platform* platform, const char* path, const DebugOptions& options) { - options_ = options; - wait_ = options.wait_for_connect(); - - auto env = parent_env_; - inspector_ = new V8NodeInspector(this, env, platform); + path_ = path == nullptr ? "" : path; + debug_options_ = options; + inspector_console_ = false; + inspector_ = + std::unique_ptr( + new NodeInspectorClient(parent_env_, platform)); platform_ = platform; - if (path != nullptr) - script_name_ = path; + Local process = parent_env_->process_object(); + Local inspector = Object::New(parent_env_->isolate()); + Local name = + FIXED_ONE_BYTE_STRING(parent_env_->isolate(), "inspector"); + process->DefineOwnProperty(parent_env_->context(), + name, + inspector, + v8::ReadOnly).FromJust(); + parent_env_->SetMethod(inspector, "wrapConsoleCall", + InspectorWrapConsoleCall); + if (options.inspector_enabled()) { + if (options.wait_for_connect()) { + parent_env_->SetMethod(inspector, "callAndPauseOnStart", + CallAndPauseOnStart); + } + return StartIoThread(); + } else { + CHECK_EQ(0, uv_async_init(uv_default_loop(), + &start_inspector_thread_async, + StartInspectorIoThreadAsyncCallback)); + start_inspector_thread_async.data = this; + uv_unref(reinterpret_cast(&start_inspector_thread_async)); + + RegisterDebugSignalHandler(); + return true; + } +} - InstallInspectorOnProcess(); +bool AgentImpl::StartIoThread() { + if (io_ != nullptr) + return true; - int err = uv_thread_create(&thread_, AgentImpl::ThreadCbIO, this); - CHECK_EQ(err, 0); - uv_sem_wait(&start_sem_); + CHECK_NE(inspector_, nullptr); - if (state_ == State::kError) { - Stop(); + inspector_console_ = true; + io_ = std::unique_ptr( + new InspectorIo(parent_env_, platform_, path_, debug_options_)); + if (!io_->Start()) { + inspector_.reset(); return false; } - state_ = State::kAccepting; - if (options_.wait_for_connect()) { - DispatchMessages(); - } + + v8::Isolate* isolate = parent_env_->isolate(); + + // Send message to enable debug in workers + HandleScope handle_scope(isolate); + Local process_object = parent_env_->process_object(); + Local emit_fn = + process_object->Get(FIXED_ONE_BYTE_STRING(isolate, "emit")); + // In case the thread started early during the startup + if (!emit_fn->IsFunction()) + return true; + + Local message = Object::New(isolate); + message->Set(FIXED_ONE_BYTE_STRING(isolate, "cmd"), + FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")); + Local argv[] = { + FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), + message + }; + MakeCallback(parent_env_, process_object.As(), emit_fn.As(), + arraysize(argv), argv); + return true; } void AgentImpl::Stop() { - int err = uv_thread_join(&thread_); - CHECK_EQ(err, 0); - delete inspector_; + if (io_ != nullptr) + io_->Stop(); } -bool AgentImpl::IsConnected() { - return delegate_ != nullptr && delegate_->IsConnected(); -} - -bool AgentImpl::IsStarted() { - return !!platform_; +void AgentImpl::Connect(InspectorSessionDelegate* delegate) { + inspector_console_ = true; + inspector_->connectFrontend(delegate); } -void AgentImpl::WaitForDisconnect() { - if (state_ == State::kConnected) { - shutting_down_ = true; - Write(TransportAction::kStop, 0, StringView()); - fprintf(stderr, "Waiting for the debugger to disconnect...\n"); - fflush(stderr); - inspector_->runMessageLoopOnPause(0); - } +bool AgentImpl::IsConnected() { + return io_ && io_->IsConnected(); } -#define READONLY_PROPERTY(obj, str, var) \ - do { \ - obj->DefineOwnProperty(env->context(), \ - OneByteString(env->isolate(), str), \ - var, \ - v8::ReadOnly).FromJust(); \ - } while (0) - -void AgentImpl::InstallInspectorOnProcess() { - auto env = parent_env_; - v8::Local process = env->process_object(); - v8::Local inspector = v8::Object::New(env->isolate()); - READONLY_PROPERTY(process, "inspector", inspector); - env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall); - if (options_.wait_for_connect()) { - env->SetMethod(inspector, "callAndPauseOnStart", CallAndPauseOnStart); - } +bool AgentImpl::IsStarted() { + return !!inspector_; } // static @@ -512,209 +554,20 @@ void AgentImpl::CallAndPauseOnStart( args.GetReturnValue().Set(retval.ToLocalChecked()); } -std::unique_ptr ToProtocolString(v8::Local value) { - if (value.IsEmpty() || value->IsNull() || value->IsUndefined() || - !value->IsString()) { - return StringBuffer::create(StringView()); +void AgentImpl::WaitForDisconnect() { + if (io_ != nullptr) { + io_->WaitForDisconnect(); } - v8::Local string_value = v8::Local::Cast(value); - size_t len = string_value->Length(); - std::basic_string buffer(len, '\0'); - string_value->Write(&buffer[0], 0, len); - return StringBuffer::create(StringView(buffer.data(), len)); } -void AgentImpl::FatalException(v8::Local error, - v8::Local message) { +void AgentImpl::FatalException(Local error, + Local message) { if (!IsStarted()) return; - auto env = parent_env_; - v8::Local context = env->context(); - - int script_id = message->GetScriptOrigin().ScriptID()->Value(); - - v8::Local stack_trace = message->GetStackTrace(); - - if (!stack_trace.IsEmpty() && - stack_trace->GetFrameCount() > 0 && - script_id == stack_trace->GetFrame(0)->GetScriptId()) { - script_id = 0; - } - - const uint8_t DETAILS[] = "Uncaught"; - - inspector_->inspector()->exceptionThrown( - context, - StringView(DETAILS, sizeof(DETAILS) - 1), - error, - ToProtocolString(message->Get())->string(), - ToProtocolString(message->GetScriptResourceName())->string(), - message->GetLineNumber(context).FromMaybe(0), - message->GetStartColumn(context).FromMaybe(0), - inspector_->inspector()->createStackTrace(stack_trace), - script_id); + inspector_->FatalException(error, message); WaitForDisconnect(); } -// static -void AgentImpl::ThreadCbIO(void* agent) { - static_cast(agent)->WorkerRunIO(); -} - -// static -void AgentImpl::WriteCbIO(uv_async_t* async) { - AgentImpl* agent = static_cast(async->data); - MessageQueue outgoing_messages; - agent->SwapBehindLock(&agent->outgoing_message_queue_, &outgoing_messages); - for (const auto& outgoing : outgoing_messages) { - switch (std::get<0>(outgoing)) { - case TransportAction::kStop: - agent->server_->Stop(nullptr); - break; - case TransportAction::kSendMessage: - std::string message = StringViewToUtf8(std::get<2>(outgoing)->string()); - agent->server_->Send(std::get<1>(outgoing), message); - break; - } - } -} - -void AgentImpl::WorkerRunIO() { - int err = uv_loop_init(&child_loop_); - CHECK_EQ(err, 0); - err = uv_async_init(&child_loop_, &io_thread_req_, AgentImpl::WriteCbIO); - CHECK_EQ(err, 0); - io_thread_req_.data = this; - std::string script_path; - if (!script_name_.empty()) { - uv_fs_t req; - if (0 == uv_fs_realpath(&child_loop_, &req, script_name_.c_str(), nullptr)) - script_path = std::string(reinterpret_cast(req.ptr)); - uv_fs_req_cleanup(&req); - } - InspectorAgentDelegate delegate(this, script_path, script_name_, wait_); - delegate_ = &delegate; - InspectorSocketServer server(&delegate, - options_.host_name(), - options_.port()); - if (!server.Start(&child_loop_)) { - state_ = State::kError; // Safe, main thread is waiting on semaphore - uv_close(reinterpret_cast(&io_thread_req_), nullptr); - uv_loop_close(&child_loop_); - uv_sem_post(&start_sem_); - return; - } - server_ = &server; - if (!wait_) { - uv_sem_post(&start_sem_); - } - uv_run(&child_loop_, UV_RUN_DEFAULT); - uv_close(reinterpret_cast(&io_thread_req_), nullptr); - server.Stop(nullptr); - server.TerminateConnections(nullptr); - uv_run(&child_loop_, UV_RUN_NOWAIT); - err = uv_loop_close(&child_loop_); - CHECK_EQ(err, 0); - delegate_ = nullptr; - server_ = nullptr; -} - -template -bool AgentImpl::AppendMessage(MessageQueue* queue, - ActionType action, int session_id, - std::unique_ptr buffer) { - Mutex::ScopedLock scoped_lock(state_lock_); - bool trigger_pumping = queue->empty(); - queue->push_back(std::make_tuple(action, session_id, std::move(buffer))); - return trigger_pumping; -} - -template -void AgentImpl::SwapBehindLock(MessageQueue* vector1, - MessageQueue* vector2) { - Mutex::ScopedLock scoped_lock(state_lock_); - vector1->swap(*vector2); -} - -void AgentImpl::PostIncomingMessage(InspectorAction action, int session_id, - const std::string& message) { - if (AppendMessage(&incoming_message_queue_, action, session_id, - Utf8ToStringView(message))) { - v8::Isolate* isolate = parent_env_->isolate(); - platform_->CallOnForegroundThread(isolate, - new DispatchOnInspectorBackendTask(this)); - isolate->RequestInterrupt(InterruptCallback, this); - CHECK_EQ(0, uv_async_send(&main_thread_req_)); - } - NotifyMessageReceived(); -} - -void AgentImpl::WaitForFrontendMessage() { - Mutex::ScopedLock scoped_lock(state_lock_); - if (incoming_message_queue_.empty()) - incoming_message_cond_.Wait(scoped_lock); -} - -void AgentImpl::NotifyMessageReceived() { - Mutex::ScopedLock scoped_lock(state_lock_); - incoming_message_cond_.Broadcast(scoped_lock); -} - -void AgentImpl::DispatchMessages() { - // This function can be reentered if there was an incoming message while - // V8 was processing another inspector request (e.g. if the user is - // evaluating a long-running JS code snippet). This can happen only at - // specific points (e.g. the lines that call inspector_ methods) - if (dispatching_messages_) - return; - dispatching_messages_ = true; - MessageQueue tasks; - do { - tasks.clear(); - SwapBehindLock(&incoming_message_queue_, &tasks); - for (const auto& task : tasks) { - StringView message = std::get<2>(task)->string(); - switch (std::get<0>(task)) { - case InspectorAction::kStartSession: - CHECK_EQ(State::kAccepting, state_); - session_id_ = std::get<1>(task); - state_ = State::kConnected; - fprintf(stderr, "Debugger attached.\n"); - inspector_->connectFrontend(); - break; - case InspectorAction::kEndSession: - CHECK_EQ(State::kConnected, state_); - if (shutting_down_) { - state_ = State::kDone; - } else { - state_ = State::kAccepting; - } - inspector_->quitMessageLoopOnPause(); - inspector_->disconnectFrontend(); - break; - case InspectorAction::kSendMessage: - inspector_->dispatchMessageFromFrontend(message); - break; - } - } - } while (!tasks.empty()); - dispatching_messages_ = false; -} - -// static -void AgentImpl::MainThreadAsyncCb(uv_async_t* req) { - AgentImpl* agent = node::ContainerOf(&AgentImpl::main_thread_req_, req); - agent->DispatchMessages(); -} - -void AgentImpl::Write(TransportAction action, int session_id, - const StringView& inspector_message) { - AppendMessage(&outgoing_message_queue_, action, session_id, - StringBuffer::create(inspector_message)); - int err = uv_async_send(&io_thread_req_); - CHECK_EQ(0, err); -} - void AgentImpl::SchedulePauseOnNextStatement(const std::string& reason) { inspector_->schedulePauseOnNextStatement(reason); } @@ -747,8 +600,8 @@ void Agent::WaitForDisconnect() { impl->WaitForDisconnect(); } -void Agent::FatalException(v8::Local error, - v8::Local message) { +void Agent::FatalException(Local error, + Local message) { impl->FatalException(error, message); } @@ -756,61 +609,28 @@ void Agent::SchedulePauseOnNextStatement(const std::string& reason) { impl->SchedulePauseOnNextStatement(reason); } -InspectorAgentDelegate::InspectorAgentDelegate(AgentImpl* agent, - const std::string& script_path, - const std::string& script_name, - bool wait) - : agent_(agent), - connected_(false), - session_id_(0), - script_name_(script_name), - script_path_(script_path), - target_id_(GenerateID()), - waiting_(wait) { } - - -bool InspectorAgentDelegate::StartSession(int session_id, - const std::string& target_id) { - if (connected_) - return false; - connected_ = true; - session_id_++; - agent_->PostIncomingMessage(InspectorAction::kStartSession, session_id, ""); - return true; -} - -void InspectorAgentDelegate::MessageReceived(int session_id, - const std::string& message) { - // TODO(pfeldman): Instead of blocking execution while debugger - // engages, node should wait for the run callback from the remote client - // and initiate its startup. This is a change to node.cc that should be - // upstreamed separately. - if (waiting_) { - if (message.find("\"Runtime.runIfWaitingForDebugger\"") != - std::string::npos) { - waiting_ = false; - agent_->ResumeStartup(); - } - } - agent_->PostIncomingMessage(InspectorAction::kSendMessage, session_id, - message); +void Agent::Connect(InspectorSessionDelegate* delegate) { + impl->Connect(delegate); } -void InspectorAgentDelegate::EndSession(int session_id) { - connected_ = false; - agent_->PostIncomingMessage(InspectorAction::kEndSession, session_id, ""); +void Agent::Dispatch(const StringView& message) { + CHECK_NE(impl->client(), nullptr); + impl->client()->dispatchMessageFromFrontend(message); } -std::vector InspectorAgentDelegate::GetTargetIds() { - return { target_id_ }; +void Agent::Disconnect() { + CHECK_NE(impl->client(), nullptr); + impl->client()->disconnectFrontend(); } -std::string InspectorAgentDelegate::GetTargetTitle(const std::string& id) { - return script_name_.empty() ? GetProcessTitle() : script_name_; +InspectorSessionDelegate* Agent::delegate() { + CHECK_NE(impl->client(), nullptr); + return impl->client()->delegate(); } -std::string InspectorAgentDelegate::GetTargetUrl(const std::string& id) { - return "file://" + script_path_; +void Agent::RunMessageLoop() { + CHECK_NE(impl->client(), nullptr); + impl->client()->runMessageLoopOnPause(CONTEXT_GROUP_ID); } } // namespace inspector diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 50575d7c282d45..141998a064a2ca 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -22,20 +22,28 @@ class Value; class Message; } // namespace v8 +namespace v8_inspector { +class StringView; +} // namespace v8_inspector + namespace node { namespace inspector { class AgentImpl; +class InspectorSessionDelegate { + public: + virtual bool WaitForFrontendMessage() = 0; + virtual void OnMessage(const v8_inspector::StringView& message) = 0; +}; + class Agent { public: explicit Agent(node::Environment* env); ~Agent(); - // Start the inspector agent thread bool Start(v8::Platform* platform, const char* path, const DebugOptions& options); - // Stop the inspector agent void Stop(); bool IsStarted(); @@ -44,8 +52,14 @@ class Agent { void FatalException(v8::Local error, v8::Local message); void SchedulePauseOnNextStatement(const std::string& reason); + void Connect(InspectorSessionDelegate* delegate); + void Disconnect(); + void Dispatch(const v8_inspector::StringView& message); + InspectorSessionDelegate* delegate(); + void RunMessageLoop(); private: AgentImpl* impl; + friend class AgentImpl; }; } // namespace inspector diff --git a/src/inspector_io.cc b/src/inspector_io.cc new file mode 100644 index 00000000000000..dfb119e996c8f9 --- /dev/null +++ b/src/inspector_io.cc @@ -0,0 +1,441 @@ +#include "inspector_io.h" + +#include "inspector_socket_server.h" +#include "env.h" +#include "env-inl.h" +#include "node.h" +#include "node_crypto.h" +#include "node_mutex.h" +#include "v8-inspector.h" +#include "util.h" +#include "zlib.h" + +#include +#include + +#include +#include + + +namespace node { +namespace inspector { +namespace { +using v8_inspector::StringBuffer; +using v8_inspector::StringView; + +template +using TransportAndIo = std::pair; + +std::string GetProcessTitle() { + // uv_get_process_title will trim the title if it is too long. + char title[2048]; + int err = uv_get_process_title(title, sizeof(title)); + if (err == 0) { + return title; + } else { + return "Node.js"; + } +} + +// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt +// Used ver 4 - with numbers +std::string GenerateID() { + uint16_t buffer[8]; + CHECK(crypto::EntropySource(reinterpret_cast(buffer), + sizeof(buffer))); + + char uuid[256]; + snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + buffer[0], // time_low + buffer[1], // time_mid + buffer[2], // time_low + (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version + (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low + buffer[5], // node + buffer[6], + buffer[7]); + return uuid; +} + +std::string StringViewToUtf8(const StringView& view) { + if (view.is8Bit()) { + return std::string(reinterpret_cast(view.characters8()), + view.length()); + } + const uint16_t* source = view.characters16(); + const UChar* unicodeSource = reinterpret_cast(source); + static_assert(sizeof(*source) == sizeof(*unicodeSource), + "sizeof(*source) == sizeof(*unicodeSource)"); + + size_t result_length = view.length() * sizeof(*source); + std::string result(result_length, '\0'); + UnicodeString utf16(unicodeSource, view.length()); + // ICU components for std::string compatibility are not enabled in build... + bool done = false; + while (!done) { + CheckedArrayByteSink sink(&result[0], result_length); + utf16.toUTF8(sink); + result_length = sink.NumberOfBytesAppended(); + result.resize(result_length); + done = !sink.Overflowed(); + } + return result; +} + +void HandleSyncCloseCb(uv_handle_t* handle) { + *static_cast(handle->data) = true; +} + +int CloseAsyncAndLoop(uv_async_t* async) { + bool is_closed = false; + async->data = &is_closed; + uv_close(reinterpret_cast(async), HandleSyncCloseCb); + while (!is_closed) + uv_run(async->loop, UV_RUN_ONCE); + async->data = nullptr; + return uv_loop_close(async->loop); +} + +} // namespace + +std::unique_ptr Utf8ToStringView(const std::string& message) { + UnicodeString utf16 = + UnicodeString::fromUTF8(StringPiece(message.data(), message.length())); + StringView view(reinterpret_cast(utf16.getBuffer()), + utf16.length()); + return StringBuffer::create(view); +} + + +class IoSessionDelegate : public InspectorSessionDelegate { + public: + explicit IoSessionDelegate(InspectorIo* io) : io_(io) { } + bool WaitForFrontendMessage() override; + void OnMessage(const v8_inspector::StringView& message) override; + private: + InspectorIo* io_; +}; + +class InspectorIoDelegate: public node::inspector::SocketServerDelegate { + public: + InspectorIoDelegate(InspectorIo* io, const std::string& script_path, + const std::string& script_name, bool wait); + bool StartSession(int session_id, const std::string& target_id) override; + void MessageReceived(int session_id, const std::string& message) override; + void EndSession(int session_id) override; + std::vector GetTargetIds() override; + std::string GetTargetTitle(const std::string& id) override; + std::string GetTargetUrl(const std::string& id) override; + bool IsConnected() { return connected_; } + private: + InspectorIo* io_; + bool connected_; + int session_id_; + const std::string script_name_; + const std::string script_path_; + const std::string target_id_; + bool waiting_; +}; + +void InterruptCallback(v8::Isolate*, void* io) { + static_cast(io)->DispatchMessages(); +} + +class DispatchOnInspectorBackendTask : public v8::Task { + public: + explicit DispatchOnInspectorBackendTask(InspectorIo* io) : io_(io) {} + + void Run() override { + io_->DispatchMessages(); + } + + private: + InspectorIo* io_; +}; + +InspectorIo::InspectorIo(Environment* env, v8::Platform* platform, + const std::string& path, const DebugOptions& options) + : options_(options), delegate_(nullptr), + shutting_down_(false), state_(State::kNew), + parent_env_(env), io_thread_req_(), + platform_(platform), dispatching_messages_(false), + session_id_(0), script_name_(path) { + CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_, + InspectorIo::MainThreadAsyncCb)); + uv_unref(reinterpret_cast(&main_thread_req_)); + CHECK_EQ(0, uv_sem_init(&start_sem_, 0)); +} + +bool InspectorIo::Start() { + CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadCbIO, this), 0); + uv_sem_wait(&start_sem_); + + if (state_ == State::kError) { + Stop(); + return false; + } + state_ = State::kAccepting; + if (options_.wait_for_connect()) { + DispatchMessages(); + } + return true; +} + +void InspectorIo::Stop() { + int err = uv_thread_join(&thread_); + CHECK_EQ(err, 0); +} + +bool InspectorIo::IsConnected() { + return delegate_ != nullptr && delegate_->IsConnected(); +} + +bool InspectorIo::IsStarted() { + return platform_ != nullptr; +} + +void InspectorIo::WaitForDisconnect() { + if (state_ == State::kConnected) { + shutting_down_ = true; + Write(TransportAction::kStop, 0, StringView()); + fprintf(stderr, "Waiting for the debugger to disconnect...\n"); + fflush(stderr); + parent_env_->inspector_agent()->RunMessageLoop(); + } +} + +// static +void InspectorIo::ThreadCbIO(void* io) { + static_cast(io)->WorkerRunIO(); +} + +// static +template +void InspectorIo::WriteCbIO(uv_async_t* async) { + TransportAndIo* io_and_transport = + static_cast*>(async->data); + if (io_and_transport == nullptr) { + return; + } + MessageQueue outgoing_messages; + InspectorIo* io = io_and_transport->second; + io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_messages); + for (const auto& outgoing : outgoing_messages) { + switch (std::get<0>(outgoing)) { + case TransportAction::kStop: + io_and_transport->first->Stop(nullptr); + break; + case TransportAction::kSendMessage: + std::string message = StringViewToUtf8(std::get<2>(outgoing)->string()); + io_and_transport->first->Send(std::get<1>(outgoing), message); + break; + } + } +} + +template +void InspectorIo::WorkerRunIO() { + uv_loop_t loop; + int err = uv_loop_init(&loop); + CHECK_EQ(err, 0); + io_thread_req_.data = nullptr; + err = uv_async_init(&loop, &io_thread_req_, WriteCbIO); + CHECK_EQ(err, 0); + std::string script_path; + if (!script_name_.empty()) { + uv_fs_t req; + if (0 == uv_fs_realpath(&loop, &req, script_name_.c_str(), nullptr)) + script_path = std::string(static_cast(req.ptr)); + uv_fs_req_cleanup(&req); + } + InspectorIoDelegate delegate(this, script_path, script_name_, + options_.wait_for_connect()); + delegate_ = &delegate; + InspectorSocketServer server(&delegate, + options_.host_name(), + options_.port()); + TransportAndIo queue_transport(&server, this); + io_thread_req_.data = &queue_transport; + if (!server.Start(&loop)) { + state_ = State::kError; // Safe, main thread is waiting on semaphore + CHECK_EQ(0, CloseAsyncAndLoop(&io_thread_req_)); + uv_sem_post(&start_sem_); + return; + } + if (!options_.wait_for_connect()) { + uv_sem_post(&start_sem_); + } + uv_run(&loop, UV_RUN_DEFAULT); + io_thread_req_.data = nullptr; + server.Stop(nullptr); + server.TerminateConnections(nullptr); + CHECK_EQ(CloseAsyncAndLoop(&io_thread_req_), 0); + delegate_ = nullptr; +} + +template +bool InspectorIo::AppendMessage(MessageQueue* queue, + ActionType action, int session_id, + std::unique_ptr buffer) { + Mutex::ScopedLock scoped_lock(state_lock_); + bool trigger_pumping = queue->empty(); + queue->push_back(std::make_tuple(action, session_id, std::move(buffer))); + return trigger_pumping; +} + +template +void InspectorIo::SwapBehindLock(MessageQueue* vector1, + MessageQueue* vector2) { + Mutex::ScopedLock scoped_lock(state_lock_); + vector1->swap(*vector2); +} + +void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id, + const std::string& message) { + if (AppendMessage(&incoming_message_queue_, action, session_id, + Utf8ToStringView(message))) { + v8::Isolate* isolate = parent_env_->isolate(); + platform_->CallOnForegroundThread(isolate, + new DispatchOnInspectorBackendTask(this)); + isolate->RequestInterrupt(InterruptCallback, this); + CHECK_EQ(0, uv_async_send(&main_thread_req_)); + } + NotifyMessageReceived(); +} + +void InspectorIo::WaitForFrontendMessage() { + Mutex::ScopedLock scoped_lock(state_lock_); + if (incoming_message_queue_.empty()) + incoming_message_cond_.Wait(scoped_lock); +} + +void InspectorIo::NotifyMessageReceived() { + Mutex::ScopedLock scoped_lock(state_lock_); + incoming_message_cond_.Broadcast(scoped_lock); +} + +void InspectorIo::DispatchMessages() { + // This function can be reentered if there was an incoming message while + // V8 was processing another inspector request (e.g. if the user is + // evaluating a long-running JS code snippet). This can happen only at + // specific points (e.g. the lines that call inspector_ methods) + if (dispatching_messages_) + return; + dispatching_messages_ = true; + MessageQueue tasks; + do { + tasks.clear(); + SwapBehindLock(&incoming_message_queue_, &tasks); + for (const auto& task : tasks) { + StringView message = std::get<2>(task)->string(); + switch (std::get<0>(task)) { + case InspectorAction::kStartSession: + CHECK_EQ(session_delegate_, nullptr); + session_id_ = std::get<1>(task); + state_ = State::kConnected; + fprintf(stderr, "Debugger attached.\n"); + session_delegate_ = std::unique_ptr( + new IoSessionDelegate(this)); + parent_env_->inspector_agent()->Connect(session_delegate_.get()); + break; + case InspectorAction::kEndSession: + CHECK_NE(session_delegate_, nullptr); + if (shutting_down_) { + state_ = State::kDone; + } else { + state_ = State::kAccepting; + } + parent_env_->inspector_agent()->Disconnect(); + session_delegate_.reset(); + break; + case InspectorAction::kSendMessage: + parent_env_->inspector_agent()->Dispatch(message); + break; + } + } + } while (!tasks.empty()); + dispatching_messages_ = false; +} + +// static +void InspectorIo::MainThreadAsyncCb(uv_async_t* req) { + InspectorIo* io = node::ContainerOf(&InspectorIo::main_thread_req_, req); + io->DispatchMessages(); +} + +void InspectorIo::Write(TransportAction action, int session_id, + const StringView& inspector_message) { + AppendMessage(&outgoing_message_queue_, action, session_id, + StringBuffer::create(inspector_message)); + int err = uv_async_send(&io_thread_req_); + CHECK_EQ(0, err); +} + +InspectorIoDelegate::InspectorIoDelegate(InspectorIo* io, + const std::string& script_path, + const std::string& script_name, + bool wait) + : io_(io), + connected_(false), + session_id_(0), + script_name_(script_name), + script_path_(script_path), + target_id_(GenerateID()), + waiting_(wait) { } + + +bool InspectorIoDelegate::StartSession(int session_id, + const std::string& target_id) { + if (connected_) + return false; + connected_ = true; + session_id_++; + io_->PostIncomingMessage(InspectorAction::kStartSession, session_id, ""); + return true; +} + +void InspectorIoDelegate::MessageReceived(int session_id, + const std::string& message) { + // TODO(pfeldman): Instead of blocking execution while debugger + // engages, node should wait for the run callback from the remote client + // and initiate its startup. This is a change to node.cc that should be + // upstreamed separately. + if (waiting_) { + if (message.find("\"Runtime.runIfWaitingForDebugger\"") != + std::string::npos) { + waiting_ = false; + io_->ResumeStartup(); + } + } + io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id, + message); +} + +void InspectorIoDelegate::EndSession(int session_id) { + connected_ = false; + io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, ""); +} + +std::vector InspectorIoDelegate::GetTargetIds() { + return { target_id_ }; +} + +std::string InspectorIoDelegate::GetTargetTitle(const std::string& id) { + return script_name_.empty() ? GetProcessTitle() : script_name_; +} + +std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) { + return "file://" + script_path_; +} + +bool IoSessionDelegate::WaitForFrontendMessage() { + io_->WaitForFrontendMessage(); + return true; +} + +void IoSessionDelegate::OnMessage(const v8_inspector::StringView& message) { + io_->Write(TransportAction::kSendMessage, io_->session_id_, message); +} + +} // namespace inspector +} // namespace node diff --git a/src/inspector_io.h b/src/inspector_io.h new file mode 100644 index 00000000000000..20a572ac58f5ee --- /dev/null +++ b/src/inspector_io.h @@ -0,0 +1,125 @@ +#ifndef SRC_INSPECTOR_IO_H_ +#define SRC_INSPECTOR_IO_H_ + +#include "inspector_socket_server.h" +#include "node_debug_options.h" +#include "node_mutex.h" +#include "uv.h" + +#include +#include +#include + +#if !HAVE_INSPECTOR +#error("This header can only be used when inspector is enabled") +#endif + + +// Forward declaration to break recursive dependency chain with src/env.h. +namespace node { +class Environment; +} // namespace node + +namespace v8_inspector { +class StringBuffer; +class StringView; +} // namespace v8_inspector + +namespace node { +namespace inspector { + +class InspectorIoDelegate; + +enum class InspectorAction { + kStartSession, kEndSession, kSendMessage +}; + +enum class TransportAction { + kSendMessage, kStop +}; + +class InspectorIo { + public: + InspectorIo(node::Environment* env, v8::Platform* platform, + const std::string& path, const DebugOptions& options); + + // Start the inspector agent thread + bool Start(); + // Stop the inspector agent + void Stop(); + + bool IsStarted(); + bool IsConnected(); + void WaitForDisconnect(); + + void PostIncomingMessage(InspectorAction action, int session_id, + const std::string& message); + void ResumeStartup() { + uv_sem_post(&start_sem_); + } + + private: + template + using MessageQueue = + std::vector>>; + enum class State { kNew, kAccepting, kConnected, kDone, kError }; + + static void ThreadCbIO(void* agent); + static void MainThreadAsyncCb(uv_async_t* req); + + template static void WriteCbIO(uv_async_t* async); + template void WorkerRunIO(); + void SetConnected(bool connected); + void DispatchMessages(); + void Write(TransportAction action, int session_id, + const v8_inspector::StringView& message); + template + bool AppendMessage(MessageQueue* vector, ActionType action, + int session_id, + std::unique_ptr buffer); + template + void SwapBehindLock(MessageQueue* vector1, + MessageQueue* vector2); + void WaitForFrontendMessage(); + void NotifyMessageReceived(); + bool StartThread(bool wait); + + // Message queues + ConditionVariable incoming_message_cond_; + + const DebugOptions options_; + uv_sem_t start_sem_; + Mutex state_lock_; + uv_thread_t thread_; + + InspectorIoDelegate* delegate_; + bool shutting_down_; + State state_; + node::Environment* parent_env_; + + uv_async_t io_thread_req_; + uv_async_t main_thread_req_; + std::unique_ptr session_delegate_; + v8::Platform* platform_; + MessageQueue incoming_message_queue_; + MessageQueue outgoing_message_queue_; + bool dispatching_messages_; + int session_id_; + + std::string script_name_; + std::string script_path_; + const std::string id_; + + friend class DispatchOnInspectorBackendTask; + friend class IoSessionDelegate; + friend void InterruptCallback(v8::Isolate*, void* agent); +}; + +std::unique_ptr Utf8ToStringView( + const std::string& message); + +} // namespace inspector +} // namespace node + +#endif // SRC_INSPECTOR_IO_H_ diff --git a/src/node.cc b/src/node.cc index 2c937b298ef70c..e63026b8f4f82a 100644 --- a/src/node.cc +++ b/src/node.cc @@ -270,7 +270,7 @@ static struct { bool StartInspector(Environment *env, const char* script_path, const node::DebugOptions& options) { env->ThrowError("Node compiled with NODE_USE_V8_PLATFORM=0"); - return false; // make compiler happy + return true; } void StartTracingAgent() { @@ -282,7 +282,6 @@ static struct { } v8_platform; #ifdef __POSIX__ -static uv_sem_t debug_semaphore; static const unsigned kMaxSignal = 32; #endif @@ -2581,9 +2580,7 @@ void FatalException(Isolate* isolate, if (exit_code) { #if HAVE_INSPECTOR - if (debug_options.inspector_enabled()) { - env->inspector_agent()->FatalException(error, message); - } + env->inspector_agent()->FatalException(error, message); #endif exit(exit_code); } @@ -3860,10 +3857,6 @@ static void DispatchMessagesDebugAgentCallback(Environment* env) { static void StartDebug(Environment* env, const char* path, DebugOptions debug_options) { CHECK(!debugger_running); -#if HAVE_INSPECTOR - if (debug_options.inspector_enabled()) - debugger_running = v8_platform.StartInspector(env, path, debug_options); -#endif // HAVE_INSPECTOR if (debug_options.debugger_enabled()) { env->debugger_agent()->set_dispatch_handler( DispatchMessagesDebugAgentCallback); @@ -3873,6 +3866,10 @@ static void StartDebug(Environment* env, const char* path, debug_options.host_name().c_str(), debug_options.port()); fflush(stderr); } +#if HAVE_INSPECTOR + } else { + debugger_running = v8_platform.StartInspector(env, path, debug_options); +#endif // HAVE_INSPECTOR } } @@ -3881,10 +3878,6 @@ static void StartDebug(Environment* env, const char* path, static void EnableDebug(Environment* env) { CHECK(debugger_running); - if (!debug_options.debugger_enabled()) { - return; - } - // Send message to enable debug in workers HandleScope handle_scope(env->isolate()); @@ -3902,16 +3895,6 @@ static void EnableDebug(Environment* env) { } -// Called from an arbitrary thread. -static void TryStartDebugger() { - Mutex::ScopedLock scoped_lock(node_isolate_mutex); - if (auto isolate = node_isolate) { - v8::Debug::DebugBreak(isolate); - uv_async_send(&dispatch_debug_messages_async); - } -} - - // Called from the main thread. static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) { Mutex::ScopedLock scoped_lock(node_isolate_mutex); @@ -3934,11 +3917,6 @@ static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) { #ifdef __POSIX__ -static void EnableDebugSignalHandler(int signo) { - uv_sem_post(&debug_semaphore); -} - - void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler) { @@ -3972,112 +3950,16 @@ void DebugProcess(const FunctionCallbackInfo& args) { return env->ThrowErrnoException(errno, "kill"); } } - - -inline void* DebugSignalThreadMain(void* unused) { - for (;;) { - uv_sem_wait(&debug_semaphore); - TryStartDebugger(); - } - return nullptr; -} - - -static int RegisterDebugSignalHandler() { - // Start a watchdog thread for calling v8::Debug::DebugBreak() because - // it's not safe to call directly from the signal handler, it can - // deadlock with the thread it interrupts. - CHECK_EQ(0, uv_sem_init(&debug_semaphore, 0)); - pthread_attr_t attr; - CHECK_EQ(0, pthread_attr_init(&attr)); - // Don't shrink the thread's stack on FreeBSD. Said platform decided to - // follow the pthreads specification to the letter rather than in spirit: - // https://lists.freebsd.org/pipermail/freebsd-current/2014-March/048885.html -#ifndef __FreeBSD__ - CHECK_EQ(0, pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN)); -#endif // __FreeBSD__ - CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); - sigset_t sigmask; - sigfillset(&sigmask); - CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask)); - pthread_t thread; - const int err = - pthread_create(&thread, &attr, DebugSignalThreadMain, nullptr); - CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr)); - CHECK_EQ(0, pthread_attr_destroy(&attr)); - if (err != 0) { - fprintf(stderr, "node[%d]: pthread_create: %s\n", getpid(), strerror(err)); - fflush(stderr); - // Leave SIGUSR1 blocked. We don't install a signal handler, - // receiving the signal would terminate the process. - return -err; - } - RegisterSignalHandler(SIGUSR1, EnableDebugSignalHandler); - // Unblock SIGUSR1. A pending SIGUSR1 signal will now be delivered. - sigemptyset(&sigmask); - sigaddset(&sigmask, SIGUSR1); - CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr)); - return 0; -} #endif // __POSIX__ #ifdef _WIN32 -DWORD WINAPI EnableDebugThreadProc(void* arg) { - TryStartDebugger(); - return 0; -} - - static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf, size_t buf_len) { return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); } -static int RegisterDebugSignalHandler() { - wchar_t mapping_name[32]; - HANDLE mapping_handle; - DWORD pid; - LPTHREAD_START_ROUTINE* handler; - - pid = GetCurrentProcessId(); - - if (GetDebugSignalHandlerMappingName(pid, - mapping_name, - arraysize(mapping_name)) < 0) { - return -1; - } - - mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE, - nullptr, - PAGE_READWRITE, - 0, - sizeof *handler, - mapping_name); - if (mapping_handle == nullptr) { - return -1; - } - - handler = reinterpret_cast( - MapViewOfFile(mapping_handle, - FILE_MAP_ALL_ACCESS, - 0, - 0, - sizeof *handler)); - if (handler == nullptr) { - CloseHandle(mapping_handle); - return -1; - } - - *handler = EnableDebugThreadProc; - - UnmapViewOfFile(static_cast(handler)); - - return 0; -} - - static void DebugProcess(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = args.GetIsolate(); @@ -4177,7 +4059,7 @@ static void DebugEnd(const FunctionCallbackInfo& args) { if (debugger_running) { Environment* env = Environment::GetCurrent(args); #if HAVE_INSPECTOR - if (debug_options.inspector_enabled()) { + if (!debug_options.debugger_enabled()) { env->inspector_agent()->Stop(); } else { #endif @@ -4373,10 +4255,6 @@ void Init(int* argc, const char no_typed_array_heap[] = "--typed_array_max_size_in_heap=0"; V8::SetFlagsFromString(no_typed_array_heap, sizeof(no_typed_array_heap) - 1); - if (!debug_options.debugger_enabled() && !debug_options.inspector_enabled()) { - RegisterDebugSignalHandler(); - } - // We should set node_is_initialized here instead of in node::Start, // otherwise embedders using node::Init to initialize everything will not be // able to set it and native modules will not load for them. @@ -4490,16 +4368,13 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, Environment env(isolate_data, context); env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling); - bool debug_enabled = - debug_options.debugger_enabled() || debug_options.inspector_enabled(); + const char* path = argc > 1 ? argv[1] : nullptr; + StartDebug(&env, path, debug_options); - // Start debug agent when argv has --debug - if (debug_enabled) { - const char* path = argc > 1 ? argv[1] : nullptr; - StartDebug(&env, path, debug_options); - if (!debugger_running) - return 12; // Signal internal error. - } + bool debugger_enabled = + debug_options.debugger_enabled() || debug_options.inspector_enabled(); + if (debugger_enabled && !debugger_running) + return 12; // Signal internal error. { Environment::AsyncCallbackScope callback_scope(&env); @@ -4509,7 +4384,7 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, env.set_trace_sync_io(trace_sync_io); // Enable debugger - if (debug_enabled) + if (debug_options.debugger_enabled()) EnableDebug(&env); if (load_napi_modules) { diff --git a/src/node_debug_options.cc b/src/node_debug_options.cc index 97f9dce1b7fc79..d559ed6ec9fecb 100644 --- a/src/node_debug_options.cc +++ b/src/node_debug_options.cc @@ -134,10 +134,10 @@ bool DebugOptions::ParseOption(const std::string& option) { int DebugOptions::port() const { int port = port_; if (port < 0) { -#if HAVE_INSPECTOR - port = inspector_enabled_ ? default_inspector_port : default_debugger_port; -#else port = default_debugger_port; +#if HAVE_INSPECTOR + if (!debugger_enabled_ || inspector_enabled_) + port = default_inspector_port; #endif // HAVE_INSPECTOR } return port;