#include "inspector_agent.h" #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR #include "env.h" #include "inspector_io.h" #include "node.h" #include "util.h" #include "v8-inspector.h" #include "v8-platform.h" #include "zlib.h" #include "libplatform/libplatform.h" #include #include #include #ifdef __POSIX__ #include // setuid, getuid #endif // __POSIX__ namespace node { namespace inspector { namespace { using v8::Context; using v8::External; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::NewStringType; using v8::Object; using v8::Persistent; using v8::String; using v8::Value; using v8_inspector::StringBuffer; using v8_inspector::StringView; using v8_inspector::V8Inspector; using v8_inspector::V8InspectorClient; static uv_sem_t start_io_thread_semaphore; static uv_async_t start_io_thread_async; class StartIoTask : public v8::Task { public: explicit StartIoTask(Agent *agent) : agent(agent) {} void Run() override { agent->StartIoThread(false); } private: Agent *agent; }; std::unique_ptr ToProtocolString(Isolate *isolate, Local value) { TwoByteValue buffer(isolate, value); return StringBuffer::create(StringView(*buffer, buffer.length())); } // Called on the main thread. void StartIoThreadAsyncCallback(uv_async_t *handle) { static_cast(handle->data)->StartIoThread(false); } void StartIoInterrupt(Isolate *isolate, void *agent) { static_cast(agent)->StartIoThread(false); } #ifdef __POSIX__ static void StartIoThreadWakeup(int signo) { uv_sem_post(&start_io_thread_semaphore); } inline void *StartIoThreadMain(void *unused) { for (;;) { uv_sem_wait(&start_io_thread_semaphore); Agent *agent = static_cast(start_io_thread_async.data); if (agent != nullptr) agent->RequestIoThreadStart(); } return nullptr; } static int StartDebugSignalHandler() { // 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(&start_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; // Mask all signals. sigfillset(&sigmask); CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask)); pthread_t thread; const int err = pthread_create(&thread, &attr, StartIoThreadMain, nullptr); // Restore original mask CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr)); CHECK_EQ(0, pthread_attr_destroy(&attr)); if (err != 0) { SE_LOGE("node[%d]: pthread_create: %s\n", getpid(), strerror(err)); // Leave SIGUSR1 blocked. We don't install a signal handler, // receiving the signal would terminate the process. return -err; } RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup); // 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 StartIoThreadProc(void *arg) { Agent *agent = static_cast(start_io_thread_async.data); if (agent != nullptr) agent->RequestIoThreadStart(); 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 StartDebugSignalHandler() { 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 = StartIoThreadProc; UnmapViewOfFile(static_cast(handler)); return 0; } #endif // _WIN32 class JsBindingsSessionDelegate : public InspectorSessionDelegate { public: JsBindingsSessionDelegate(Environment *env, Local session, Local receiver, Local callback) : env_(env), session_(env->isolate(), session), receiver_(env->isolate(), receiver), callback_(env->isolate(), callback) { session_.SetWeak(this, JsBindingsSessionDelegate::Release, v8::WeakCallbackType::kParameter); } ~JsBindingsSessionDelegate() override { session_.Reset(); receiver_.Reset(); callback_.Reset(); } bool WaitForFrontendMessageWhilePaused() override { return false; } void SendMessageToFrontend(const v8_inspector::StringView &message) override { Isolate *isolate = env_->isolate(); v8::HandleScope handle_scope(isolate); Context::Scope context_scope(env_->context()); MaybeLocal v8string = String::NewFromTwoByte(isolate, message.characters16(), NewStringType::kNormal, static_cast(message.length())); Local argument = v8string.ToLocalChecked().As(); Local callback = callback_.Get(isolate); Local receiver = receiver_.Get(isolate); callback->Call(env_->context(), receiver, 1, &argument) .FromMaybe(Local()); } void Disconnect() { Agent *agent = env_->inspector_agent(); if (agent->delegate() == this) agent->Disconnect(); } private: static void Release( const v8::WeakCallbackInfo &info) { info.SetSecondPassCallback(ReleaseSecondPass); info.GetParameter()->session_.Reset(); } static void ReleaseSecondPass( const v8::WeakCallbackInfo &info) { JsBindingsSessionDelegate *delegate = info.GetParameter(); delegate->Disconnect(); delete delegate; } Environment *env_; Persistent session_; Persistent receiver_; Persistent callback_; }; void SetDelegate(Environment *env, Local inspector, JsBindingsSessionDelegate *delegate) { inspector->SetPrivate(env->context(), env->inspector_delegate_private_symbol(), v8::External::New(env->isolate(), delegate)); } Maybe GetDelegate( const FunctionCallbackInfo &info) { Environment *env = Environment::GetCurrent(info); Local delegate; MaybeLocal maybe_delegate = info.This()->GetPrivate(env->context(), env->inspector_delegate_private_symbol()); if (maybe_delegate.ToLocal(&delegate)) { CHECK(delegate->IsExternal()); void *value = delegate.As()->Value(); if (value != nullptr) { return v8::Just(static_cast(value)); } } env->ThrowError("Inspector is not connected"); return v8::Nothing(); } void Dispatch(const FunctionCallbackInfo &info) { Environment *env = Environment::GetCurrent(info); if (!info[0]->IsString()) { env->ThrowError("Inspector message must be a string"); return; } Maybe maybe_delegate = GetDelegate(info); if (maybe_delegate.IsNothing()) return; Agent *inspector = env->inspector_agent(); CHECK_EQ(maybe_delegate.ToChecked(), inspector->delegate()); inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string()); } void Disconnect(const FunctionCallbackInfo &info) { Environment *env = Environment::GetCurrent(info); Maybe delegate = GetDelegate(info); if (delegate.IsNothing()) { return; } delegate.ToChecked()->Disconnect(); SetDelegate(env, info.This(), nullptr); delete delegate.ToChecked(); } void ConnectJSBindingsSession(const FunctionCallbackInfo &info) { Environment *env = Environment::GetCurrent(info); if (!info[0]->IsFunction()) { env->ThrowError("Message callback is required"); return; } Agent *inspector = env->inspector_agent(); if (inspector->delegate() != nullptr) { env->ThrowError("Session is already attached"); return; } Local session = Object::New(env->isolate()); env->SetMethod(session, "dispatch", Dispatch); env->SetMethod(session, "disconnect", Disconnect); info.GetReturnValue().Set(session); JsBindingsSessionDelegate *delegate = new JsBindingsSessionDelegate(env, session, info.Holder(), info[0].As()); inspector->Connect(delegate); SetDelegate(env, session, delegate); } void InspectorConsoleCall(const v8::FunctionCallbackInfo &info) { Isolate *isolate = info.GetIsolate(); HandleScope handle_scope(isolate); Local context = isolate->GetCurrentContext(); CHECK_LT(2, info.Length()); std::vector> call_args; for (int i = 3; i < info.Length(); ++i) { call_args.push_back(info[i]); } Environment *env = Environment::GetCurrent(isolate); if (env->inspector_agent()->enabled()) { Local inspector_method = info[0]; CHECK(inspector_method->IsFunction()); Local config_value = info[2]; 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(), static_cast(call_args.size()), call_args.data()) .IsEmpty()); } CHECK(config_object->Delete(context, in_call_key).FromJust()); } Local node_method = info[1]; CHECK(node_method->IsFunction()); node_method.As()->Call(context, info.Holder(), static_cast(call_args.size()), call_args.data()) .FromMaybe(Local()); } void CallAndPauseOnStart( const v8::FunctionCallbackInfo &args) { Environment *env = Environment::GetCurrent(args); CHECK_GT(args.Length(), 1); CHECK(args[0]->IsFunction()); std::vector> call_args; for (int i = 2; i < args.Length(); i++) { call_args.push_back(args[i]); } env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start"); v8::MaybeLocal retval = args[0].As()->Call(env->context(), args[1], static_cast(call_args.size()), call_args.data()); if (!retval.IsEmpty()) { args.GetReturnValue().Set(retval.ToLocalChecked()); } } // Used in NodeInspectorClient::currentTimeMS() below. const int NANOS_PER_MSEC = 1000000; const int CONTEXT_GROUP_ID = 1; class ChannelImpl final : public v8_inspector::V8Inspector::Channel { public: explicit ChannelImpl(V8Inspector *inspector, InspectorSessionDelegate *delegate) : delegate_(delegate) { #if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 3) session_ = inspector->connect(1, this, StringView(), v8_inspector::V8Inspector::ClientTrustLevel::kFullyTrusted); #else session_ = inspector->connect(1, this, StringView()); #endif } virtual ~ChannelImpl() {} void dispatchProtocolMessage(const StringView &message) { session_->dispatchProtocolMessage(message); } bool waitForFrontendMessage() { return delegate_->WaitForFrontendMessageWhilePaused(); } void schedulePauseOnNextStatement(const std::string &reason) { std::unique_ptr buffer = Utf8ToStringView(reason); session_->schedulePauseOnNextStatement(buffer->string(), buffer->string()); } InspectorSessionDelegate *delegate() { return delegate_; } private: void sendResponse( int callId, std::unique_ptr message) override { sendMessageToFrontend(message->string()); } void sendNotification( std::unique_ptr message) override { sendMessageToFrontend(message->string()); } void flushProtocolNotifications() override {} void sendMessageToFrontend(const StringView &message) { delegate_->SendMessageToFrontend(message); } InspectorSessionDelegate *const delegate_; std::unique_ptr session_; }; class InspectorTimer { public: InspectorTimer(uv_loop_t *loop, double interval_s, V8InspectorClient::TimerCallback callback, void *data) : timer_(), callback_(callback), data_(data) { uv_timer_init(loop, &timer_); int64_t interval_ms = 1000 * interval_s; uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms); } InspectorTimer(const InspectorTimer &) = delete; void Stop() { uv_timer_stop(&timer_); uv_close(reinterpret_cast(&timer_), TimerClosedCb); } private: static void OnTimer(uv_timer_t *uvtimer) { InspectorTimer *timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer); timer->callback_(timer->data_); } static void TimerClosedCb(uv_handle_t *uvtimer) { InspectorTimer *timer = node::ContainerOf(&InspectorTimer::timer_, reinterpret_cast(uvtimer)); delete timer; } ~InspectorTimer() {} uv_timer_t timer_; V8InspectorClient::TimerCallback callback_; void *data_; }; class InspectorTimerHandle { public: InspectorTimerHandle(uv_loop_t *loop, double interval_s, V8InspectorClient::TimerCallback callback, void *data) { timer_ = new InspectorTimer(loop, interval_s, callback, data); } InspectorTimerHandle(const InspectorTimerHandle &) = delete; ~InspectorTimerHandle() { CHECK_NE(timer_, nullptr); timer_->Stop(); timer_ = nullptr; } private: InspectorTimer *timer_; }; } // namespace class NodeInspectorClient : public V8InspectorClient { public: NodeInspectorClient(node::Environment *env, v8::Platform *platform) : env_(env), platform_(platform), terminated_(false), running_nested_loop_(false) { client_ = V8Inspector::create(env->isolate(), this); } void runMessageLoopOnPause(int context_group_id) override { CHECK_NE(channel_, nullptr); if (running_nested_loop_) return; terminated_ = false; running_nested_loop_ = true; while (!terminated_ && channel_->waitForFrontendMessage()) { while (v8::platform::PumpMessageLoop(platform_, env_->isolate())) { } } terminated_ = false; running_nested_loop_ = false; } double currentTimeMS() override { return uv_hrtime() * 1.0 / NANOS_PER_MSEC; } void contextCreated(Local context, const std::string &name) { std::unique_ptr name_buffer = Utf8ToStringView(name); v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID, name_buffer->string()); client_->contextCreated(info); } void contextDestroyed(Local context) { client_->contextDestroyed(context); } void quitMessageLoopOnPause() override { terminated_ = true; } void connectFrontend(InspectorSessionDelegate *delegate) { CHECK_EQ(channel_, nullptr); channel_ = std::unique_ptr( new ChannelImpl(client_.get(), delegate)); } void disconnectFrontend() { quitMessageLoopOnPause(); channel_.reset(); } void dispatchMessageFromFrontend(const StringView &message) { if (channel_ == nullptr) return; CHECK_NE(channel_, nullptr); channel_->dispatchProtocolMessage(message); } Local ensureDefaultContextInGroup(int contextGroupId) override { return env_->context(); } void FatalException(Local error, Local message) { Local context = env_->context(); int script_id = message->GetScriptOrigin().ScriptId(); Local stack_trace = message->GetStackTrace(); if (!stack_trace.IsEmpty() && stack_trace->GetFrameCount() > 0 && script_id == stack_trace->GetFrame(env_->isolate(), 0)->GetScriptId()) { script_id = 0; } const uint8_t DETAILS[] = "Uncaught"; Isolate *isolate = context->GetIsolate(); client_->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), client_->createStackTrace(stack_trace), script_id); } ChannelImpl *channel() { return channel_.get(); } void startRepeatingTimer(double interval_s, TimerCallback callback, void *data) override { timers_.emplace(std::piecewise_construct, std::make_tuple(data), std::make_tuple(env_->event_loop(), interval_s, callback, data)); } void cancelTimer(void *data) override { timers_.erase(data); } private: node::Environment *env_; v8::Platform *platform_; bool terminated_; bool running_nested_loop_; std::unique_ptr client_; std::unique_ptr channel_; std::unordered_map timers_; }; Agent::Agent(Environment *env) : parent_env_(env), client_(nullptr), platform_(nullptr), enabled_(false) {} // Destructor needs to be defined here in implementation file as the header // does not have full definition of some classes. Agent::~Agent() { } bool Agent::Start(v8::Platform *platform, const char *path, const DebugOptions &options) { path_ = path == nullptr ? "" : path; debug_options_ = options; client_ = std::unique_ptr( new NodeInspectorClient(parent_env_, platform)); client_->contextCreated(parent_env_->context(), "Node.js Main Context"); platform_ = platform; CHECK_EQ(0, uv_async_init(uv_default_loop(), &start_io_thread_async, StartIoThreadAsyncCallback)); start_io_thread_async.data = this; uv_unref(reinterpret_cast(&start_io_thread_async)); // Ignore failure, SIGUSR1 won't work, but that should not block node start. StartDebugSignalHandler(); if (options.inspector_enabled()) { // This will return false if listen failed on the inspector port. return StartIoThread(options.wait_for_connect()); } return true; } bool Agent::StartIoThread(bool wait_for_connect) { if (io_ != nullptr) return true; CHECK_NE(client_, nullptr); enabled_ = true; io_ = std::unique_ptr( new InspectorIo(parent_env_, platform_, path_, debug_options_, wait_for_connect)); if (!io_->Start()) { client_.reset(); return false; } 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(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "emit")).ToLocalChecked(); // In case the thread started early during the startup if (!emit_fn->IsFunction()) return true; Local message = Object::New(isolate); message->Set(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "cmd"), FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")) .Check(); Local argv[] = { FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), message}; MakeCallback(parent_env_->isolate(), process_object, emit_fn.As(), arraysize(argv), argv, {0, 0}); return true; } void Agent::Stop() { if (io_ != nullptr) { io_->Stop(); io_.reset(); } } void Agent::Connect(InspectorSessionDelegate *delegate) { enabled_ = true; client_->connectFrontend(delegate); } bool Agent::IsConnected() { return io_ && io_->IsConnected(); } void Agent::WaitForDisconnect() { CHECK_NE(client_, nullptr); client_->contextDestroyed(parent_env_->context()); if (io_ != nullptr) { io_->WaitForDisconnect(); } } void Agent::FatalException(Local error, Local message) { if (!IsStarted()) return; client_->FatalException(error, message); WaitForDisconnect(); } void Agent::Dispatch(const StringView &message) { if (client_ == nullptr) return; CHECK_NE(client_, nullptr); client_->dispatchMessageFromFrontend(message); } void Agent::Disconnect() { CHECK_NE(client_, nullptr); client_->disconnectFrontend(); } void Agent::RunMessageLoop() { CHECK_NE(client_, nullptr); client_->runMessageLoopOnPause(CONTEXT_GROUP_ID); } InspectorSessionDelegate *Agent::delegate() { CHECK_NE(client_, nullptr); ChannelImpl *channel = client_->channel(); if (channel == nullptr) return nullptr; return channel->delegate(); } void Agent::PauseOnNextJavascriptStatement(const std::string &reason) { ChannelImpl *channel = client_->channel(); if (channel != nullptr) channel->schedulePauseOnNextStatement(reason); } void Open(const FunctionCallbackInfo &args) { Environment *env = Environment::GetCurrent(args); inspector::Agent *agent = env->inspector_agent(); bool wait_for_connect = false; if (args.Length() > 0 && args[0]->IsUint32()) { uint32_t port = args[0]->Uint32Value(env->context()).ToChecked(); agent->options().set_port(static_cast(port)); } if (args.Length() > 1 && args[1]->IsString()) { node::Utf8Value host(env->isolate(), args[1].As()); agent->options().set_host_name(*host); } if (args.Length() > 2 && args[2]->IsBoolean()) { wait_for_connect = args[2]->BooleanValue(env->isolate()); } agent->StartIoThread(wait_for_connect); } void Url(const FunctionCallbackInfo &args) { Environment *env = Environment::GetCurrent(args); inspector::Agent *agent = env->inspector_agent(); inspector::InspectorIo *io = agent->io(); if (!io) return; std::vector ids = io->GetTargetIds(); if (ids.empty()) return; std::string url = FormatWsAddress(io->host(), io->port(), ids[0], true); args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str())); } // static void Agent::InitInspector(Local target, Local unused, Local context, void *priv) { Environment *env = Environment::GetCurrent(context); Agent *agent = env->inspector_agent(); env->SetMethod(target, "consoleCall", InspectorConsoleCall); if (agent->debug_options_.wait_for_connect()) env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "connect", ConnectJSBindingsSession); env->SetMethod(target, "open", Open); env->SetMethod(target, "url", Url); } void Agent::RequestIoThreadStart() { // We need to attempt to interrupt V8 flow (in case Node is running // continuous JS code) and to wake up libuv thread (in case Node is waiting // for IO events) uv_async_send(&start_io_thread_async); v8::Isolate *isolate = parent_env_->isolate(); platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique(this)); isolate->RequestInterrupt(StartIoInterrupt, this); uv_async_send(&start_io_thread_async); } } // namespace inspector } // namespace node //cjh NODE_MODULE_CONTEXT_AWARE_BUILTIN(inspector, // node::inspector::Agent::InitInspector); #endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR