summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEugene Ostroukhov <eostroukhov@chromium.org>2016-12-12 17:08:31 -0800
committerEugene Ostroukhov <eostroukhov@chromium.org>2017-04-06 09:10:29 -0700
commit7599b0ef9dcd28dd47e0c876cf51bf65fa15c73d (patch)
tree5ba8773aaa247b27dfc24824a1ffc4a78fae4f32 /src
parent47f8f7462fb198aa27ede602c43786bdbfda37a2 (diff)
downloadandroid-node-v8-7599b0ef9dcd28dd47e0c876cf51bf65fa15c73d.tar.gz
android-node-v8-7599b0ef9dcd28dd47e0c876cf51bf65fa15c73d.tar.bz2
android-node-v8-7599b0ef9dcd28dd47e0c876cf51bf65fa15c73d.zip
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 <info@bnoordhuis.nl>
Diffstat (limited to 'src')
-rw-r--r--src/inspector_agent.cc924
-rw-r--r--src/inspector_agent.h18
-rw-r--r--src/inspector_io.cc441
-rw-r--r--src/inspector_io.h125
-rw-r--r--src/node.cc153
-rw-r--r--src/node_debug_options.cc6
6 files changed, 971 insertions, 696 deletions
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index 34ba5a7fc9..dac4c10495 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 <map>
-#include <sstream>
-#include <tuple>
-#include <unicode/unistr.h>
-
#include <string.h>
-#include <utility>
#include <vector>
+#ifdef __POSIX__
+#include <unistd.h> // 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<StringBuffer> ToProtocolString(Isolate* isolate,
+ Local<Value> 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<unsigned char*>(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<const char*>(view.characters8()),
- view.length());
- }
- const uint16_t* source = view.characters16();
- const UChar* unicodeSource = reinterpret_cast<const UChar*>(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<StringBuffer> Utf8ToStringView(const std::string& message) {
- UnicodeString utf16 =
- UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
- StringView view(reinterpret_cast<const uint16_t*>(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<std::string> 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<LPTHREAD_START_ROUTINE*>(
+ MapViewOfFile(mapping_handle,
+ FILE_MAP_ALL_ACCESS,
+ 0,
+ 0,
+ sizeof *handler));
+ if (handler == nullptr) {
+ CloseHandle(mapping_handle);
+ return -1;
+ }
+
+ *handler = EnableDebugThreadProc;
+
+ UnmapViewOfFile(static_cast<void*>(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<v8::Value> error,
- v8::Local<v8::Message> message);
+ void FatalException(Local<Value> error,
+ Local<v8::Message> 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<v8::Value>& args);
-
- void InstallInspectorOnProcess();
+ static void CallAndPauseOnStart(const v8::FunctionCallbackInfo<v8::Value>&);
void WorkerRunIO();
void SetConnected(bool connected);
- void DispatchMessages();
- void Write(TransportAction action, int session_id, const StringView& message);
- template <typename ActionType>
- bool AppendMessage(MessageQueue<ActionType>* vector, ActionType action,
- int session_id, std::unique_ptr<StringBuffer> buffer);
- template <typename ActionType>
- void SwapBehindLock(MessageQueue<ActionType>* vector1,
- MessageQueue<ActionType>* vector2);
void WaitForFrontendMessage();
void NotifyMessageReceived();
+ bool StartIoThread();
+ static void InspectorWrapConsoleCall(
+ const v8::FunctionCallbackInfo<Value>& args);
+ static void InspectorConsoleCall(
+ const v8::FunctionCallbackInfo<Value>& 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<NodeInspectorClient> inspector_;
+ std::unique_ptr<InspectorIo> io_;
v8::Platform* platform_;
- MessageQueue<InspectorAction> incoming_message_queue_;
- MessageQueue<TransportAction> 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<AgentImpl*>(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<StringBuffer> 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<v8_inspector::V8InspectorSession> 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<ChannelImpl>(
+ 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<StringBuffer> buffer = Utf8ToStringView(reason);
- session_->schedulePauseOnNextStatement(buffer->string(),
- buffer->string());
+ if (channel_ != nullptr) {
+ channel_->schedulePauseOnNextStatement(reason);
}
}
- v8::Local<v8::Context> ensureDefaultContextInGroup(int contextGroupId)
- override {
+ Local<Context> ensureDefaultContextInGroup(int contextGroupId) override {
return env_->context();
}
- V8Inspector* inspector() {
- return inspector_.get();
+ void FatalException(Local<Value> error, Local<v8::Message> message) {
+ Local<Context> context = env_->context();
+
+ int script_id = message->GetScriptOrigin().ScriptID()->Value();
+
+ Local<v8::StackTrace> 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<V8Inspector> inspector_;
- std::unique_ptr<v8_inspector::V8InspectorSession> session_;
+ std::unique_ptr<ChannelImpl> 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<uv_handle_t*>(&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<v8::Value>& info) {
- v8::Isolate* isolate = info.GetIsolate();
- v8::Local<v8::Context> context = isolate->GetCurrentContext();
+// static
+void AgentImpl::InspectorConsoleCall(
+ const v8::FunctionCallbackInfo<Value>& info) {
+ Isolate* isolate = info.GetIsolate();
+ Local<Context> context = isolate->GetCurrentContext();
CHECK(info.Data()->IsArray());
- v8::Local<v8::Array> args = info.Data().As<v8::Array>();
+ Local<v8::Array> args = info.Data().As<v8::Array>();
CHECK_EQ(args->Length(), 3);
- v8::Local<v8::Value> inspector_method =
- args->Get(context, 0).ToLocalChecked();
- CHECK(inspector_method->IsFunction());
- v8::Local<v8::Value> node_method =
- args->Get(context, 1).ToLocalChecked();
- CHECK(node_method->IsFunction());
- v8::Local<v8::Value> config_value =
- args->Get(context, 2).ToLocalChecked();
- CHECK(config_value->IsObject());
- v8::Local<v8::Object> config_object = config_value.As<v8::Object>();
-
- std::vector<v8::Local<v8::Value>> call_args(info.Length());
+ std::vector<Local<Value>> call_args(info.Length());
for (int i = 0; i < info.Length(); ++i) {
call_args[i] = info[i];
}
- v8::Local<v8::String> 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<v8::Function>()->Call(
- context,
- info.Holder(),
- call_args.size(),
- call_args.data()).IsEmpty());
+ Environment* env = Environment::GetCurrent(isolate);
+ if (env->inspector_agent()->impl->inspector_console_) {
+ Local<Value> inspector_method = args->Get(context, 0).ToLocalChecked();
+ CHECK(inspector_method->IsFunction());
+ Local<Value> config_value = args->Get(context, 2).ToLocalChecked();
+ CHECK(config_value->IsObject());
+ Local<Object> config_object = config_value.As<Object>();
+ Local<String> 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<Function>()->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<void>(node_method.As<v8::Function>()->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<Value> node_method =
+ args->Get(context, 1).ToLocalChecked();
+ CHECK(node_method->IsFunction());
+ static_cast<void>(node_method.As<Function>()->Call(context,
+ info.Holder(),
+ call_args.size(),
+ call_args.data()));
}
-void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
+// static
+void AgentImpl::InspectorWrapConsoleCall(
+ const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (args.Length() != 3 || !args[0]->IsFunction() ||
@@ -413,84 +427,112 @@ void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
"arguments: two functions and an object.");
}
- v8::Local<v8::Array> array = v8::Array::New(env->isolate(), args.Length());
+ Local<v8::Array> 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<AgentImpl*>(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<NodeInspectorClient>(
+ new NodeInspectorClient(parent_env_, platform));
platform_ = platform;
- if (path != nullptr)
- script_name_ = path;
+ Local<Object> process = parent_env_->process_object();
+ Local<Object> inspector = Object::New(parent_env_->isolate());
+ Local<String> 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<uv_handle_t*>(&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<InspectorIo>(
+ 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<Object> process_object = parent_env_->process_object();
+ Local<Value> 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<Object> message = Object::New(isolate);
+ message->Set(FIXED_ONE_BYTE_STRING(isolate, "cmd"),
+ FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED"));
+ Local<Value> argv[] = {
+ FIXED_ONE_BYTE_STRING(isolate, "internalMessage"),
+ message
+ };
+ MakeCallback(parent_env_, process_object.As<Value>(), emit_fn.As<Function>(),
+ 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<v8::Object> process = env->process_object();
- v8::Local<v8::Object> 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<StringBuffer> ToProtocolString(v8::Local<v8::Value> value) {
- if (value.IsEmpty() || value->IsNull() || value->IsUndefined() ||
- !value->IsString()) {
- return StringBuffer::create(StringView());
+void AgentImpl::WaitForDisconnect() {
+ if (io_ != nullptr) {
+ io_->WaitForDisconnect();
}
- v8::Local<v8::String> string_value = v8::Local<v8::String>::Cast(value);
- size_t len = string_value->Length();
- std::basic_string<uint16_t> buffer(len, '\0');
- string_value->Write(&buffer[0], 0, len);
- return StringBuffer::create(StringView(buffer.data(), len));
}
-void AgentImpl::FatalException(v8::Local<v8::Value> error,
- v8::Local<v8::Message> message) {
+void AgentImpl::FatalException(Local<Value> error,
+ Local<v8::Message> message) {
if (!IsStarted())
return;
- auto env = parent_env_;
- v8::Local<v8::Context> context = env->context();
-
- int script_id = message->GetScriptOrigin().ScriptID()->Value();
-
- v8::Local<v8::StackTrace> 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<AgentImpl*>(agent)->WorkerRunIO();
-}
-
-// static
-void AgentImpl::WriteCbIO(uv_async_t* async) {
- AgentImpl* agent = static_cast<AgentImpl*>(async->data);
- MessageQueue<TransportAction> 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<char*>(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<uv_handle_t*>(&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<uv_handle_t*>(&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 <typename ActionType>
-bool AgentImpl::AppendMessage(MessageQueue<ActionType>* queue,
- ActionType action, int session_id,
- std::unique_ptr<StringBuffer> 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 <typename ActionType>
-void AgentImpl::SwapBehindLock(MessageQueue<ActionType>* vector1,
- MessageQueue<ActionType>* 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<InspectorAction> 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<v8::Value> error,
- v8::Local<v8::Message> message) {
+void Agent::FatalException(Local<Value> error,
+ Local<v8::Message> 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<std::string> 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 50575d7c28..141998a064 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<v8::Value> error,
v8::Local<v8::Message> 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 0000000000..dfb119e996
--- /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 <sstream>
+#include <unicode/unistr.h>
+
+#include <string.h>
+#include <vector>
+
+
+namespace node {
+namespace inspector {
+namespace {
+using v8_inspector::StringBuffer;
+using v8_inspector::StringView;
+
+template<typename Transport>
+using TransportAndIo = std::pair<Transport*, InspectorIo*>;
+
+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<unsigned char*>(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<const char*>(view.characters8()),
+ view.length());
+ }
+ const uint16_t* source = view.characters16();
+ const UChar* unicodeSource = reinterpret_cast<const UChar*>(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<bool*>(handle->data) = true;
+}
+
+int CloseAsyncAndLoop(uv_async_t* async) {
+ bool is_closed = false;
+ async->data = &is_closed;
+ uv_close(reinterpret_cast<uv_handle_t*>(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<StringBuffer> Utf8ToStringView(const std::string& message) {
+ UnicodeString utf16 =
+ UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
+ StringView view(reinterpret_cast<const uint16_t*>(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<std::string> 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<InspectorIo*>(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<uv_handle_t*>(&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<InspectorIo*>(io)->WorkerRunIO<InspectorSocketServer>();
+}
+
+// static
+template <typename Transport>
+void InspectorIo::WriteCbIO(uv_async_t* async) {
+ TransportAndIo<Transport>* io_and_transport =
+ static_cast<TransportAndIo<Transport>*>(async->data);
+ if (io_and_transport == nullptr) {
+ return;
+ }
+ MessageQueue<TransportAction> 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<typename Transport>
+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<Transport>);
+ 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<char*>(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<Transport> 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 <typename ActionType>
+bool InspectorIo::AppendMessage(MessageQueue<ActionType>* queue,
+ ActionType action, int session_id,
+ std::unique_ptr<StringBuffer> 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 <typename ActionType>
+void InspectorIo::SwapBehindLock(MessageQueue<ActionType>* vector1,
+ MessageQueue<ActionType>* 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<InspectorAction> 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<InspectorSessionDelegate>(
+ 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<std::string> 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 0000000000..20a572ac58
--- /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 <memory>
+#include <stddef.h>
+#include <vector>
+
+#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 <typename Action>
+ using MessageQueue =
+ std::vector<std::tuple<Action, int,
+ std::unique_ptr<v8_inspector::StringBuffer>>>;
+ enum class State { kNew, kAccepting, kConnected, kDone, kError };
+
+ static void ThreadCbIO(void* agent);
+ static void MainThreadAsyncCb(uv_async_t* req);
+
+ template <typename Transport> static void WriteCbIO(uv_async_t* async);
+ template <typename Transport> void WorkerRunIO();
+ void SetConnected(bool connected);
+ void DispatchMessages();
+ void Write(TransportAction action, int session_id,
+ const v8_inspector::StringView& message);
+ template <typename ActionType>
+ bool AppendMessage(MessageQueue<ActionType>* vector, ActionType action,
+ int session_id,
+ std::unique_ptr<v8_inspector::StringBuffer> buffer);
+ template <typename ActionType>
+ void SwapBehindLock(MessageQueue<ActionType>* vector1,
+ MessageQueue<ActionType>* 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<InspectorSessionDelegate> session_delegate_;
+ v8::Platform* platform_;
+ MessageQueue<InspectorAction> incoming_message_queue_;
+ MessageQueue<TransportAction> 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<v8_inspector::StringBuffer> 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 2c937b298e..e63026b8f4 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<Value>& 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<LPTHREAD_START_ROUTINE*>(
- MapViewOfFile(mapping_handle,
- FILE_MAP_ALL_ACCESS,
- 0,
- 0,
- sizeof *handler));
- if (handler == nullptr) {
- CloseHandle(mapping_handle);
- return -1;
- }
-
- *handler = EnableDebugThreadProc;
-
- UnmapViewOfFile(static_cast<void*>(handler));
-
- return 0;
-}
-
-
static void DebugProcess(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
@@ -4177,7 +4059,7 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& 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 97f9dce1b7..d559ed6ec9 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;