summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPavel Feldman <pfeldman@chromium.org>2016-02-07 08:47:14 -0800
committerAli Ijaz Sheikh <ofrobots@google.com>2016-05-30 09:05:46 -0700
commit84ad31fff369f0ec9a972c2a11cdefee316dab9d (patch)
treec95e0be1802b6f1d3e167f74901efe287e1b0887 /src
parented2eacbc426ddbb57fa3faf35996b309d71b968e (diff)
downloadandroid-node-v8-84ad31fff369f0ec9a972c2a11cdefee316dab9d.tar.gz
android-node-v8-84ad31fff369f0ec9a972c2a11cdefee316dab9d.tar.bz2
android-node-v8-84ad31fff369f0ec9a972c2a11cdefee316dab9d.zip
src,lib: v8-inspector support
This change introduces experimental v8-inspector support. This brings the DevTools debug protocol allowing Node.js to be debugged with Chrome DevTools native, or through other debuggers supporting that protocol. Partial WebSocket support, to the extent required by DevTools, is included. This is derived from the implementation in Blink. v8-inspector support can be disabled by the --without-inspector configure flag. PR-URL: https://github.com/nodejs/node/pull/6792 Reviewed-By: jasnell - James M Snell <jasnell@gmail.com> Reviewed-By: addaleax - Anna Henningsen <anna@addaleax.net> Reviewed-By: bnoordhuis - Ben Noordhuis <info@bnoordhuis.nl>
Diffstat (limited to 'src')
-rw-r--r--src/env-inl.h3
-rw-r--r--src/env.h12
-rw-r--r--src/inspector_agent.cc506
-rw-r--r--src/inspector_agent.h97
-rw-r--r--src/inspector_socket.cc679
-rw-r--r--src/inspector_socket.h57
-rw-r--r--src/node.cc67
-rw-r--r--src/node_internals.h6
-rw-r--r--src/signal_wrap.cc9
9 files changed, 1431 insertions, 5 deletions
diff --git a/src/env-inl.h b/src/env-inl.h
index 34f9bf7d72..97e1ba8f76 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -225,6 +225,9 @@ inline Environment::Environment(v8::Local<v8::Context> context,
makecallback_cntr_(0),
async_wrap_uid_(0),
debugger_agent_(this),
+#if HAVE_INSPECTOR
+ inspector_agent_(this),
+#endif
http_parser_buffer_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
diff --git a/src/env.h b/src/env.h
index 0c95abd56c..4c310c8831 100644
--- a/src/env.h
+++ b/src/env.h
@@ -5,6 +5,9 @@
#include "ares.h"
#include "debug-agent.h"
+#if HAVE_INSPECTOR
+#include "inspector_agent.h"
+#endif
#include "handle_wrap.h"
#include "req-wrap.h"
#include "tree.h"
@@ -549,6 +552,12 @@ class Environment {
return &debugger_agent_;
}
+#if HAVE_INSPECTOR
+ inline inspector::Agent* inspector_agent() {
+ return &inspector_agent_;
+ }
+#endif
+
typedef ListHead<HandleWrap, &HandleWrap::handle_wrap_queue_> HandleWrapQueue;
typedef ListHead<ReqWrap<uv_req_t>, &ReqWrap<uv_req_t>::req_wrap_queue_>
ReqWrapQueue;
@@ -586,6 +595,9 @@ class Environment {
size_t makecallback_cntr_;
int64_t async_wrap_uid_;
debugger::Agent debugger_agent_;
+#if HAVE_INSPECTOR
+ inspector::Agent inspector_agent_;
+#endif
HandleWrapQueue handle_wrap_queue_;
ReqWrapQueue req_wrap_queue_;
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
new file mode 100644
index 0000000000..cd2ae83b19
--- /dev/null
+++ b/src/inspector_agent.cc
@@ -0,0 +1,506 @@
+#include "inspector_agent.h"
+
+#include "node.h"
+#include "env.h"
+#include "env-inl.h"
+#include "node_version.h"
+#include "v8-platform.h"
+#include "util.h"
+
+#include "platform/v8_inspector/public/V8Inspector.h"
+#include "platform/inspector_protocol/FrontendChannel.h"
+#include "platform/inspector_protocol/String16.h"
+#include "platform/inspector_protocol/Values.h"
+
+#include "libplatform/libplatform.h"
+
+#include <string.h>
+
+// We need pid to use as ID with Chrome
+#if defined(_MSC_VER)
+#include <direct.h>
+#include <io.h>
+#define getpid GetCurrentProcessId
+#else
+#include <unistd.h> // setuid, getuid
+#endif
+
+namespace node {
+namespace {
+
+const char DEVTOOLS_PATH[] = "/node";
+
+void PrintDebuggerReadyMessage(int port) {
+ fprintf(stderr, "Debugger listening on port %d. "
+ "To start debugging, open the following URL in Chrome:\n"
+ " chrome-devtools://devtools/remote/serve_file/"
+ "@521e5b7e2b7cc66b4006a8a54cb9c4e57494a5ef/inspector.html?"
+ "experiments=true&v8only=true&ws=localhost:%d/node\n", port, port);
+}
+
+bool AcceptsConnection(inspector_socket_t* socket, const char* path) {
+ return strncmp(DEVTOOLS_PATH, path, sizeof(DEVTOOLS_PATH)) == 0;
+}
+
+void DisposeInspector(inspector_socket_t* socket, int status) {
+ free(socket);
+}
+
+void DisconnectAndDisposeIO(inspector_socket_t* socket) {
+ if (socket) {
+ inspector_close(socket, DisposeInspector);
+ }
+}
+
+void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
+ if (len > 0) {
+ buf->base = static_cast<char*>(malloc(len));
+ CHECK_NE(buf->base, nullptr);
+ }
+ buf->len = len;
+}
+
+void SendHttpResponse(inspector_socket_t* socket, const char* response,
+ size_t len) {
+ const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/json; charset=UTF-8\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Content-Length: %ld\r\n"
+ "\r\n";
+ char header[sizeof(HEADERS) + 20];
+ int header_len = snprintf(header, sizeof(header), HEADERS, len);
+ inspector_write(socket, header, header_len);
+ inspector_write(socket, response, len);
+}
+
+void SendVersionResponse(inspector_socket_t* socket) {
+ const char VERSION_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"Browser\": \"node.js/%s\","
+ " \"Protocol-Version\": \"1.1\","
+ " \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
+ "(KHTML, like Gecko) Chrome/45.0.2446.0 Safari/537.36\","
+ " \"WebKit-Version\": \"537.36 (@198122)\""
+ "} ]";
+ char buffer[sizeof(VERSION_RESPONSE_TEMPLATE) + 128];
+ size_t len = snprintf(buffer, sizeof(buffer), VERSION_RESPONSE_TEMPLATE,
+ NODE_VERSION);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+void SendTargentsListResponse(inspector_socket_t* socket) {
+ const char LIST_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"description\": \"node.js instance\","
+ " \"devtoolsFrontendUrl\": "
+ "\"https://chrome-devtools-frontend.appspot.com/serve_file/"
+ "@4604d24a75168768584760ba56d175507941852f/inspector.html\","
+ " \"faviconUrl\": \"https://nodejs.org/static/favicon.ico\","
+ " \"id\": \"%d\","
+ " \"title\": \"%s\","
+ " \"type\": \"node\","
+ " \"webSocketDebuggerUrl\": \"ws://%s\""
+ "} ]";
+ char buffer[sizeof(LIST_RESPONSE_TEMPLATE) + 4096];
+ char title[2048]; // uv_get_process_title trims the title if too long
+ int err = uv_get_process_title(title, sizeof(title));
+ ASSERT_EQ(0, err);
+ char* c = title;
+ while (!c) {
+ if (*c < ' ' || *c == '\"') {
+ *c = '_';
+ }
+ c++;
+ }
+ size_t len = snprintf(buffer, sizeof(buffer), LIST_RESPONSE_TEMPLATE,
+ getpid(), title, DEVTOOLS_PATH);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+bool RespondToGet(inspector_socket_t* socket, const char* path) {
+ const char PATH[] = "/json";
+ const char PATH_LIST[] = "/json/list";
+ const char PATH_VERSION[] = "/json/version";
+ const char PATH_ACTIVATE[] = "/json/activate/";
+ if (!strncmp(PATH_VERSION, path, sizeof(PATH_VERSION))) {
+ SendVersionResponse(socket);
+ } else if (!strncmp(PATH_LIST, path, sizeof(PATH_LIST)) ||
+ !strncmp(PATH, path, sizeof(PATH))) {
+ SendTargentsListResponse(socket);
+ } else if (!strncmp(path, PATH_ACTIVATE, sizeof(PATH_ACTIVATE) - 1) &&
+ atoi(path + (sizeof(PATH_ACTIVATE) - 1)) == getpid()) {
+ const char TARGET_ACTIVATED[] = "Target activated";
+ SendHttpResponse(socket, TARGET_ACTIVATED, sizeof(TARGET_ACTIVATED) - 1);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace inspector {
+
+using blink::protocol::DictionaryValue;
+using blink::protocol::String16;
+
+void InterruptCallback(v8::Isolate*, void* agent) {
+ static_cast<Agent*>(agent)->PostMessages();
+}
+
+class DispatchOnInspectorBackendTask : public v8::Task {
+ public:
+ explicit DispatchOnInspectorBackendTask(Agent* agent) : agent_(agent) {}
+
+ void Run() override {
+ agent_->PostMessages();
+ }
+
+ private:
+ Agent* agent_;
+};
+
+class ChannelImpl final : public blink::protocol::FrontendChannel {
+ public:
+ explicit ChannelImpl(Agent* agent): agent_(agent) {}
+ virtual ~ChannelImpl() {}
+ private:
+ virtual void sendProtocolResponse(int sessionId, int callId,
+ std::unique_ptr<DictionaryValue> message)
+ override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void sendProtocolNotification(
+ std::unique_ptr<DictionaryValue> message) override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void flush() override { }
+
+ void sendMessageToFrontend(std::unique_ptr<DictionaryValue> message) {
+ agent_->Write(message->toJSONString().utf8());
+ }
+
+ Agent* const agent_;
+};
+
+class SetConnectedTask : public v8::Task {
+ public:
+ SetConnectedTask(Agent* agent, bool connected)
+ : agent_(agent),
+ connected_(connected) {}
+
+ void Run() override {
+ agent_->SetConnected(connected_);
+ }
+
+ private:
+ Agent* agent_;
+ bool connected_;
+};
+
+class V8NodeInspector : public blink::V8Inspector {
+ public:
+ V8NodeInspector(Agent* agent, node::Environment* env, v8::Platform* platform)
+ : blink::V8Inspector(env->isolate(), env->context()),
+ agent_(agent),
+ isolate_(env->isolate()),
+ platform_(platform),
+ terminated_(false),
+ running_nested_loop_(false) {}
+
+ void runMessageLoopOnPause(int context_group_id) override {
+ if (running_nested_loop_)
+ return;
+ terminated_ = false;
+ running_nested_loop_ = true;
+ do {
+ uv_mutex_lock(&agent_->pause_lock_);
+ uv_cond_wait(&agent_->pause_cond_, &agent_->pause_lock_);
+ uv_mutex_unlock(&agent_->pause_lock_);
+ while (v8::platform::PumpMessageLoop(platform_, isolate_))
+ {}
+ } while (!terminated_);
+ terminated_ = false;
+ running_nested_loop_ = false;
+ }
+
+ void quitMessageLoopOnPause() override {
+ terminated_ = true;
+ }
+
+ private:
+ Agent* agent_;
+ v8::Isolate* isolate_;
+ v8::Platform* platform_;
+ bool terminated_;
+ bool running_nested_loop_;
+};
+
+Agent::Agent(Environment* env) : port_(9229),
+ wait_(false),
+ connected_(false),
+ shutting_down_(false),
+ parent_env_(env),
+ client_socket_(nullptr),
+ inspector_(nullptr),
+ platform_(nullptr),
+ dispatching_messages_(false) {
+ int err;
+ err = uv_sem_init(&start_sem_, 0);
+ CHECK_EQ(err, 0);
+}
+
+Agent::~Agent() {
+ if (!inspector_)
+ return;
+ uv_mutex_destroy(&queue_lock_);
+ uv_mutex_destroy(&pause_lock_);
+ uv_cond_destroy(&pause_cond_);
+ uv_close(reinterpret_cast<uv_handle_t*>(&data_written_), nullptr);
+}
+
+void Agent::Start(v8::Platform* platform, int port, bool wait) {
+ auto env = parent_env_;
+ inspector_ = new V8NodeInspector(this, env, platform);
+
+ int err;
+
+ platform_ = platform;
+
+ err = uv_loop_init(&child_loop_);
+ CHECK_EQ(err, 0);
+ err = uv_async_init(env->event_loop(), &data_written_, nullptr);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&queue_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&pause_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_cond_init(&pause_cond_);
+ CHECK_EQ(err, 0);
+
+ uv_unref(reinterpret_cast<uv_handle_t*>(&data_written_));
+
+ port_ = port;
+ wait_ = wait;
+
+ err = uv_thread_create(&thread_, Agent::ThreadCbIO, this);
+ CHECK_EQ(err, 0);
+ uv_sem_wait(&start_sem_);
+
+ if (wait) {
+ // Flush messages in case of wait to connect, see OnRemoteDataIO on how it
+ // should be fixed.
+ SetConnected(true);
+ PostMessages();
+ }
+}
+
+void Agent::Stop() {
+ // TODO(repenaxa): hop on the right thread.
+ DisconnectAndDisposeIO(client_socket_);
+ int err = uv_thread_join(&thread_);
+ CHECK_EQ(err, 0);
+
+ uv_run(&child_loop_, UV_RUN_NOWAIT);
+
+ err = uv_loop_close(&child_loop_);
+ CHECK_EQ(err, 0);
+ delete inspector_;
+}
+
+bool Agent::IsStarted() {
+ return !!platform_;
+}
+
+void Agent::WaitForDisconnect() {
+ shutting_down_ = true;
+ fprintf(stderr, "Waiting for the debugger to disconnect...\n");
+ inspector_->runMessageLoopOnPause(0);
+}
+
+// static
+void Agent::ThreadCbIO(void* agent) {
+ static_cast<Agent*>(agent)->WorkerRunIO();
+}
+
+// static
+void Agent::OnSocketConnectionIO(uv_stream_t* server, int status) {
+ if (status == 0) {
+ inspector_socket_t* socket =
+ static_cast<inspector_socket_t*>(malloc(sizeof(*socket)));
+ ASSERT_NE(nullptr, socket);
+ memset(socket, 0, sizeof(*socket));
+ socket->data = server->data;
+ if (inspector_accept(server, socket, Agent::OnInspectorHandshakeIO) != 0) {
+ free(socket);
+ }
+ }
+}
+
+// static
+bool Agent::OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path) {
+ Agent* agent = static_cast<Agent*>(socket->data);
+ switch (state) {
+ case kInspectorHandshakeHttpGet:
+ return RespondToGet(socket, path);
+ case kInspectorHandshakeUpgrading:
+ return AcceptsConnection(socket, path);
+ case kInspectorHandshakeUpgraded:
+ agent->OnInspectorConnectionIO(socket);
+ return true;
+ case kInspectorHandshakeFailed:
+ return false;
+ default:
+ UNREACHABLE();
+ }
+}
+
+// static
+void Agent::OnRemoteDataIO(uv_stream_t* stream,
+ ssize_t read,
+ const uv_buf_t* b) {
+ inspector_socket_t* socket = static_cast<inspector_socket_t*>(stream->data);
+ Agent* agent = static_cast<Agent*>(socket->data);
+ if (read > 0) {
+ std::string str(b->base, read);
+ agent->PushPendingMessage(&agent->message_queue_, str);
+ free(b->base);
+
+ // 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 (agent->wait_ && str.find("\"Runtime.run\"") != std::string::npos) {
+ agent->wait_ = false;
+ uv_sem_post(&agent->start_sem_);
+ }
+
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new DispatchOnInspectorBackendTask(agent));
+ agent->parent_env_->isolate()
+ ->RequestInterrupt(InterruptCallback, agent);
+ uv_async_send(&agent->data_written_);
+ } else if (read < 0) {
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ }
+ DisconnectAndDisposeIO(socket);
+ } else {
+ // EOF
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new SetConnectedTask(agent, false));
+ uv_async_send(&agent->data_written_);
+ }
+ }
+ uv_cond_broadcast(&agent->pause_cond_);
+}
+
+void Agent::PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message) {
+ uv_mutex_lock(&queue_lock_);
+ queue->push_back(message);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+void Agent::SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output) {
+ uv_mutex_lock(&queue_lock_);
+ (this->*queue).swap(*output);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+// static
+void Agent::WriteCbIO(uv_async_t* async) {
+ Agent* agent = static_cast<Agent*>(async->data);
+ inspector_socket_t* socket = agent->client_socket_;
+ if (socket) {
+ std::vector<std::string> outgoing_messages;
+ agent->SwapBehindLock(&Agent::outgoing_message_queue_, &outgoing_messages);
+ for (auto const& message : outgoing_messages)
+ inspector_write(socket, message.c_str(), message.length());
+ }
+}
+
+void Agent::WorkerRunIO() {
+ sockaddr_in addr;
+ uv_tcp_t server;
+ int err = uv_async_init(&child_loop_, &io_thread_req_, Agent::WriteCbIO);
+ CHECK_EQ(0, err);
+ io_thread_req_.data = this;
+ uv_tcp_init(&child_loop_, &server);
+ uv_ip4_addr("0.0.0.0", port_, &addr);
+ server.data = this;
+ err = uv_tcp_bind(&server,
+ reinterpret_cast<const struct sockaddr*>(&addr), 0);
+ if (err == 0) {
+ err = uv_listen(reinterpret_cast<uv_stream_t*>(&server), 1,
+ OnSocketConnectionIO);
+ }
+ if (err == 0) {
+ PrintDebuggerReadyMessage(port_);
+ } else {
+ fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
+ ABORT();
+ }
+ 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);
+ uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
+ uv_run(&child_loop_, UV_RUN_DEFAULT);
+}
+
+void Agent::OnInspectorConnectionIO(inspector_socket_t* socket) {
+ if (client_socket_) {
+ return;
+ }
+ client_socket_ = socket;
+ inspector_read_start(socket, OnBufferAlloc, Agent::OnRemoteDataIO);
+ platform_->CallOnForegroundThread(parent_env_->isolate(),
+ new SetConnectedTask(this, true));
+}
+
+void Agent::PostMessages() {
+ if (dispatching_messages_)
+ return;
+ dispatching_messages_ = true;
+ std::vector<std::string> messages;
+ SwapBehindLock(&Agent::message_queue_, &messages);
+ for (auto const& message : messages)
+ inspector_->dispatchMessageFromFrontend(
+ String16::fromUTF8(message.c_str(), message.length()));
+ uv_async_send(&data_written_);
+ dispatching_messages_ = false;
+}
+
+void Agent::SetConnected(bool connected) {
+ if (connected_ == connected)
+ return;
+
+ connected_ = connected;
+ if (connected) {
+ fprintf(stderr, "Debugger attached.\n");
+ inspector_->connectFrontend(new ChannelImpl(this));
+ } else {
+ if (!shutting_down_)
+ PrintDebuggerReadyMessage(port_);
+ inspector_->quitMessageLoopOnPause();
+ inspector_->disconnectFrontend();
+ }
+}
+
+void Agent::Write(const std::string& message) {
+ PushPendingMessage(&outgoing_message_queue_, message);
+ ASSERT_EQ(0, uv_async_send(&io_thread_req_));
+}
+} // namespace debugger
+} // namespace node
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
new file mode 100644
index 0000000000..65a4abeff7
--- /dev/null
+++ b/src/inspector_agent.h
@@ -0,0 +1,97 @@
+#ifndef SRC_INSPECTOR_AGENT_H_
+#define SRC_INSPECTOR_AGENT_H_
+
+#if !HAVE_INSPECTOR
+#error("This header can only be used when inspector is enabled")
+#endif
+
+#include "inspector_socket.h"
+#include "uv.h"
+#include "v8.h"
+#include "util.h"
+
+#include <string>
+#include <vector>
+
+namespace blink {
+class V8Inspector;
+}
+
+// Forward declaration to break recursive dependency chain with src/env.h.
+namespace node {
+class Environment;
+} // namespace node
+
+namespace node {
+namespace inspector {
+
+class ChannelImpl;
+
+class Agent {
+ public:
+ explicit Agent(node::Environment* env);
+ ~Agent();
+
+ // Start the inspector agent thread
+ void Start(v8::Platform* platform, int port, bool wait);
+ // Stop the inspector agent
+ void Stop();
+
+ bool IsStarted();
+ bool connected() { return connected_; }
+ void WaitForDisconnect();
+
+ protected:
+ static void ThreadCbIO(void* agent);
+ static void OnSocketConnectionIO(uv_stream_t* server, int status);
+ static bool OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path);
+ static void OnRemoteDataIO(uv_stream_t* stream, ssize_t read,
+ const uv_buf_t* b);
+ static void WriteCbIO(uv_async_t* async);
+
+ void WorkerRunIO();
+ void OnInspectorConnectionIO(inspector_socket_t* socket);
+ void PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message);
+ void SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output);
+ void PostMessages();
+ void SetConnected(bool connected);
+ void Write(const std::string& message);
+
+ uv_sem_t start_sem_;
+ uv_cond_t pause_cond_;
+ uv_mutex_t queue_lock_;
+ uv_mutex_t pause_lock_;
+ uv_thread_t thread_;
+ uv_loop_t child_loop_;
+ uv_tcp_t server_;
+
+ int port_;
+ bool wait_;
+ bool connected_;
+ bool shutting_down_;
+ node::Environment* parent_env_;
+
+ uv_async_t data_written_;
+ uv_async_t io_thread_req_;
+ inspector_socket_t* client_socket_;
+ blink::V8Inspector* inspector_;
+ v8::Platform* platform_;
+ std::vector<std::string> message_queue_;
+ std::vector<std::string> outgoing_message_queue_;
+ bool dispatching_messages_;
+
+ friend class ChannelImpl;
+ friend class DispatchOnInspectorBackendTask;
+ friend class SetConnectedTask;
+ friend class V8NodeInspector;
+ friend void InterruptCallback(v8::Isolate*, void* agent);
+};
+
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_AGENT_H_
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
new file mode 100644
index 0000000000..cb248ec59f
--- /dev/null
+++ b/src/inspector_socket.cc
@@ -0,0 +1,679 @@
+#include "inspector_socket.h"
+
+#define NODE_WANT_INTERNALS 1
+#include "base64.h"
+
+#include "openssl/sha.h" // Sha-1 hash
+
+#include <string.h>
+#include <vector>
+
+#define ACCEPT_KEY_LENGTH base64_encoded_size(20)
+#define BUFFER_GROWTH_CHUNK_SIZE 1024
+
+#define DUMP_READS 0
+#define DUMP_WRITES 0
+
+static const char CLOSE_FRAME[] = {'\x88', '\x00'};
+
+struct http_parsing_state_s {
+ http_parser parser;
+ http_parser_settings parser_settings;
+ handshake_cb callback;
+ bool parsing_value;
+ char* ws_key;
+ char* path;
+ char* current_header;
+};
+
+struct ws_state_s {
+ uv_alloc_cb alloc_cb;
+ uv_read_cb read_cb;
+ inspector_cb close_cb;
+ bool close_sent;
+ bool received_close;
+};
+
+enum ws_decode_result {
+ FRAME_OK, FRAME_INCOMPLETE, FRAME_CLOSE, FRAME_ERROR
+};
+
+#if DUMP_READS || DUMP_WRITES
+static void dump_hex(const char* buf, size_t len) {
+ const char* ptr = buf;
+ const char* end = ptr + len;
+ const char* cptr;
+ char c;
+ int i;
+
+ while (ptr < end) {
+ cptr = ptr;
+ for (i = 0; i < 16 && ptr < end; i++) {
+ printf("%2.2X ", *(ptr++));
+ }
+ for (i = 72 - (i * 4); i > 0; i--) {
+ printf(" ");
+ }
+ for (i = 0; i < 16 && cptr < end; i++) {
+ c = *(cptr++);
+ printf("%c", (c > 0x19) ? c : '.');
+ }
+ printf("\n");
+ }
+ printf("\n\n");
+}
+#endif
+
+static void dispose_inspector(uv_handle_t* handle) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(handle->data);
+ inspector_cb close =
+ inspector->ws_mode ? inspector->ws_state->close_cb : nullptr;
+ free(inspector->buffer);
+ free(inspector->ws_state);
+ inspector->ws_state = nullptr;
+ inspector->buffer = nullptr;
+ inspector->buffer_size = 0;
+ inspector->data_len = 0;
+ inspector->last_read_end = 0;
+ if (close) {
+ close(inspector, 0);
+ }
+}
+
+static void close_connection(inspector_socket_t* inspector) {
+ uv_handle_t* socket = reinterpret_cast<uv_handle_t*>(&inspector->client);
+ if (!uv_is_closing(socket)) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(socket));
+ uv_close(socket, dispose_inspector);
+ } else if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+}
+
+// Cleanup
+static void write_request_cleanup(uv_write_t* req, int status) {
+ free((reinterpret_cast<uv_buf_t*>(req->data))->base);
+ free(req->data);
+ free(req);
+}
+
+static int write_to_client(inspector_socket_t* inspector,
+ const char* msg,
+ size_t len,
+ uv_write_cb write_cb = write_request_cleanup) {
+#if DUMP_WRITES
+ printf("%s (%ld bytes):\n", __FUNCTION__, len);
+ dump_hex(msg, len);
+#endif
+
+ // Freed in write_request_cleanup
+ uv_buf_t* buf = reinterpret_cast<uv_buf_t*>(malloc(sizeof(uv_buf_t)));
+ uv_write_t* req = reinterpret_cast<uv_write_t*>(malloc(sizeof(uv_write_t)));
+ CHECK_NE(buf, nullptr);
+ CHECK_NE(req, nullptr);
+ memset(req, 0, sizeof(*req));
+ buf->base = reinterpret_cast<char*>(malloc(len));
+
+ CHECK_NE(buf->base, nullptr);
+
+ memcpy(buf->base, msg, len);
+ buf->len = len;
+ req->data = buf;
+
+ uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ return uv_write(req, stream, buf, 1, write_cb) < 0;
+}
+
+// Constants for hybi-10 frame format.
+
+typedef int OpCode;
+
+const OpCode kOpCodeContinuation = 0x0;
+const OpCode kOpCodeText = 0x1;
+const OpCode kOpCodeBinary = 0x2;
+const OpCode kOpCodeClose = 0x8;
+const OpCode kOpCodePing = 0x9;
+const OpCode kOpCodePong = 0xA;
+
+const unsigned char kFinalBit = 0x80;
+const unsigned char kReserved1Bit = 0x40;
+const unsigned char kReserved2Bit = 0x20;
+const unsigned char kReserved3Bit = 0x10;
+const unsigned char kOpCodeMask = 0xF;
+const unsigned char kMaskBit = 0x80;
+const unsigned char kPayloadLengthMask = 0x7F;
+
+const size_t kMaxSingleBytePayloadLength = 125;
+const size_t kTwoBytePayloadLengthField = 126;
+const size_t kEightBytePayloadLengthField = 127;
+const size_t kMaskingKeyWidthInBytes = 4;
+
+static std::vector<char> encode_frame_hybi17(const char* message,
+ size_t data_length) {
+ std::vector<char> frame;
+ OpCode op_code = kOpCodeText;
+ frame.push_back(kFinalBit | op_code);
+ if (data_length <= kMaxSingleBytePayloadLength) {
+ frame.push_back(static_cast<char>(data_length));
+ } else if (data_length <= 0xFFFF) {
+ frame.push_back(kTwoBytePayloadLengthField);
+ frame.push_back((data_length & 0xFF00) >> 8);
+ frame.push_back(data_length & 0xFF);
+ } else {
+ frame.push_back(kEightBytePayloadLengthField);
+ char extended_payload_length[8];
+ size_t remaining = data_length;
+ // Fill the length into extended_payload_length in the network byte order.
+ for (int i = 0; i < 8; ++i) {
+ extended_payload_length[7 - i] = remaining & 0xFF;
+ remaining >>= 8;
+ }
+ frame.insert(frame.end(), extended_payload_length,
+ extended_payload_length + 8);
+ ASSERT_EQ(0, remaining);
+ }
+ frame.insert(frame.end(), message, message + data_length);
+ return frame;
+}
+
+static ws_decode_result decode_frame_hybi17(const char* buffer_begin,
+ size_t data_length,
+ bool client_frame,
+ int* bytes_consumed,
+ std::vector<char>* output,
+ bool* compressed) {
+ *bytes_consumed = 0;
+ if (data_length < 2)
+ return FRAME_INCOMPLETE;
+
+ const char* p = buffer_begin;
+ const char* buffer_end = p + data_length;
+
+ unsigned char first_byte = *p++;
+ unsigned char second_byte = *p++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ int op_code = first_byte & kOpCodeMask;
+ bool masked = (second_byte & kMaskBit) != 0;
+ *compressed = reserved1;
+ if (!final || reserved2 || reserved3)
+ return FRAME_ERROR; // Only compression extension is supported.
+
+ bool closed = false;
+ switch (op_code) {
+ case kOpCodeClose:
+ closed = true;
+ break;
+ case kOpCodeText:
+ break;
+ case kOpCodeBinary: // We don't support binary frames yet.
+ case kOpCodeContinuation: // We don't support binary frames yet.
+ case kOpCodePing: // We don't support binary frames yet.
+ case kOpCodePong: // We don't support binary frames yet.
+ default:
+ return FRAME_ERROR;
+ }
+
+ // In Hybi-17 spec client MUST mask its frame.
+ if (client_frame && !masked) {
+ return FRAME_ERROR;
+ }
+
+ uint64_t payload_length64 = second_byte & kPayloadLengthMask;
+ if (payload_length64 > kMaxSingleBytePayloadLength) {
+ int extended_payload_length_size;
+ if (payload_length64 == kTwoBytePayloadLengthField) {
+ extended_payload_length_size = 2;
+ } else if (payload_length64 == kEightBytePayloadLengthField) {
+ extended_payload_length_size = 8;
+ } else {
+ return FRAME_ERROR;
+ }
+ if (buffer_end - p < extended_payload_length_size)
+ return FRAME_INCOMPLETE;
+ payload_length64 = 0;
+ for (int i = 0; i < extended_payload_length_size; ++i) {
+ payload_length64 <<= 8;
+ payload_length64 |= static_cast<unsigned char>(*p++);
+ }
+ }
+
+ static const uint64_t max_payload_length = 0x7FFFFFFFFFFFFFFFull;
+ static const size_t max_length = SIZE_MAX;
+ if (payload_length64 > max_payload_length ||
+ payload_length64 > max_length - kMaskingKeyWidthInBytes) {
+ // WebSocket frame length too large.
+ return FRAME_ERROR;
+ }
+ size_t payload_length = static_cast<size_t>(payload_length64);
+
+ if (data_length - kMaskingKeyWidthInBytes < payload_length)
+ return FRAME_INCOMPLETE;
+
+ const char* masking_key = p;
+ const char* payload = p + kMaskingKeyWidthInBytes;
+ for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
+ output->insert(output->end(),
+ payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]);
+
+ size_t pos = p + kMaskingKeyWidthInBytes + payload_length - buffer_begin;
+ *bytes_consumed = pos;
+ return closed ? FRAME_CLOSE : FRAME_OK;
+}
+
+static void invoke_read_callback(inspector_socket_t* inspector,
+ int status, const uv_buf_t* buf) {
+ if (inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(
+ reinterpret_cast<uv_stream_t*>(&inspector->client), status, buf);
+ }
+}
+
+static void shutdown_complete(inspector_socket_t* inspector) {
+ if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+ close_connection(inspector);
+}
+
+static void on_close_frame_written(uv_write_t* write, int status) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(write->handle->data);
+ write_request_cleanup(write, status);
+ inspector->ws_state->close_sent = true;
+ if (inspector->ws_state->received_close) {
+ shutdown_complete(inspector);
+ }
+}
+
+static void close_frame_received(inspector_socket_t* inspector) {
+ inspector->ws_state->received_close = true;
+ if (!inspector->ws_state->close_sent) {
+ invoke_read_callback(inspector, 0, 0);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ } else {
+ shutdown_complete(inspector);
+ }
+}
+
+static int parse_ws_frames(inspector_socket_t* inspector, size_t len) {
+ int bytes_consumed = 0;
+ std::vector<char> output;
+ bool compressed = false;
+
+ ws_decode_result r = decode_frame_hybi17(inspector->buffer,
+ len, true /* client_frame */,
+ &bytes_consumed, &output,
+ &compressed);
+ // Compressed frame means client is ignoring the headers and misbehaves
+ if (compressed || r == FRAME_ERROR) {
+ invoke_read_callback(inspector, UV_EPROTO, nullptr);
+ close_connection(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_CLOSE) {
+ close_frame_received(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_OK && inspector->ws_state->alloc_cb
+ && inspector->ws_state->read_cb) {
+ uv_buf_t buffer;
+ size_t len = output.size();
+ inspector->ws_state->alloc_cb(
+ reinterpret_cast<uv_handle_t*>(&inspector->client),
+ len, &buffer);
+ CHECK_GE(buffer.len, len);
+ memcpy(buffer.base, &output[0], len);
+ invoke_read_callback(inspector, len, &buffer);
+ }
+ return bytes_consumed;
+}
+
+static void prepare_buffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+
+ if (len > (inspector->buffer_size - inspector->data_len)) {
+ int new_size = (inspector->data_len + len + BUFFER_GROWTH_CHUNK_SIZE - 1) /
+ BUFFER_GROWTH_CHUNK_SIZE *
+ BUFFER_GROWTH_CHUNK_SIZE;
+ inspector->buffer_size = new_size;
+ inspector->buffer = reinterpret_cast<char*>(realloc(inspector->buffer,
+ inspector->buffer_size));
+ ASSERT_NE(inspector->buffer, nullptr);
+ }
+ buf->base = inspector->buffer + inspector->data_len;
+ buf->len = len;
+ inspector->data_len += len;
+}
+
+static void websockets_data_cb(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->connection_eof = true;
+ if (!inspector->shutting_down && inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(stream, nread, nullptr);
+ }
+ } else {
+ #if DUMP_READS
+ printf("%s read %ld bytes\n", __FUNCTION__, nread);
+ if (nread > 0) {
+ dump_hex(buf->base, nread);
+ }
+ #endif
+ // 1. Move read bytes to continue the buffer
+ // Should be same as this is supposedly last buffer
+ ASSERT_EQ(buf->base + buf->len, inspector->buffer + inspector->data_len);
+
+ // Should be noop...
+ memmove(inspector->buffer + inspector->last_read_end, buf->base, nread);
+ inspector->last_read_end += nread;
+
+ // 2. Parse.
+ int processed = 0;
+ do {
+ processed = parse_ws_frames(inspector, inspector->last_read_end);
+ // 3. Fix the buffer size & length
+ if (processed > 0) {
+ memmove(inspector->buffer, inspector->buffer + processed,
+ inspector->last_read_end - processed);
+ inspector->last_read_end -= processed;
+ inspector->data_len = inspector->last_read_end;
+ }
+ } while (processed > 0 && inspector->data_len > 0);
+ }
+}
+
+int inspector_read_start(inspector_socket_t* inspector,
+ uv_alloc_cb alloc_cb, uv_read_cb read_cb) {
+ ASSERT(inspector->ws_mode);
+ ASSERT(!inspector->shutting_down || read_cb == nullptr);
+ inspector->ws_state->close_sent = false;
+ inspector->ws_state->alloc_cb = alloc_cb;
+ inspector->ws_state->read_cb = read_cb;
+ int err =
+ uv_read_start(reinterpret_cast<uv_stream_t*>(&inspector->client),
+ prepare_buffer,
+ websockets_data_cb);
+ if (err < 0) {
+ close_connection(inspector);
+ }
+ return err;
+}
+
+void inspector_read_stop(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ inspector->ws_state->alloc_cb = nullptr;
+ inspector->ws_state->read_cb = nullptr;
+}
+
+static void generate_accept_string(const char* client_key, char* buffer) {
+ // Magic string from websockets spec.
+ const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ size_t key_len = strlen(client_key);
+ size_t magic_len = sizeof(ws_magic) - 1;
+
+ char* buf = reinterpret_cast<char*>(malloc(key_len + magic_len));
+ CHECK_NE(buf, nullptr);
+ memcpy(buf, client_key, key_len);
+ memcpy(buf + key_len, ws_magic, magic_len);
+ char hash[20];
+ SHA1((unsigned char*) buf, key_len + magic_len, (unsigned char*) hash);
+ free(buf);
+ node::base64_encode(hash, 20, buffer, ACCEPT_KEY_LENGTH);
+ buffer[ACCEPT_KEY_LENGTH] = '\0';
+}
+
+static void append(char** value, const char* string, size_t length) {
+ const size_t INCREMENT = 500; // There should never be more then 1 chunk...
+
+ int current_len = *value ? strlen(*value) : 0;
+ int new_len = current_len + length;
+ int adjusted = (new_len / INCREMENT + 1) * INCREMENT;
+ *value = reinterpret_cast<char*>(realloc(*value, adjusted));
+ memcpy(*value + current_len, string, length);
+ (*value)[new_len] = '\0';
+}
+
+static int header_value_cb(http_parser* parser, const char* at, size_t length) {
+ char SEC_WEBSOCKET_KEY_HEADER[] = "Sec-WebSocket-Key";
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ state->parsing_value = true;
+ if (state->current_header && strncmp(state->current_header,
+ SEC_WEBSOCKET_KEY_HEADER,
+ sizeof(SEC_WEBSOCKET_KEY_HEADER)) == 0) {
+ append(&state->ws_key, at, length);
+ }
+ return 0;
+}
+
+static int header_field_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ if (state->parsing_value) {
+ state->parsing_value = false;
+ if (state->current_header)
+ state->current_header[0] = '\0';
+ }
+ append(&state->current_header, at, length);
+ return 0;
+}
+
+static int path_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ append(&state->path, at, length);
+ return 0;
+}
+
+static void handshake_complete(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ handshake_cb callback = inspector->http_parsing_state->callback;
+ inspector->ws_state = (struct ws_state_s*) malloc(sizeof(struct ws_state_s));
+ ASSERT_NE(nullptr, inspector->ws_state);
+ memset(inspector->ws_state, 0, sizeof(struct ws_state_s));
+ inspector->last_read_end = 0;
+ inspector->ws_mode = true;
+ callback(inspector, kInspectorHandshakeUpgraded,
+ inspector->http_parsing_state->path);
+}
+
+static void cleanup_http_parsing_state(struct http_parsing_state_s* state) {
+ free(state->current_header);
+ free(state->path);
+ free(state->ws_key);
+ free(state);
+}
+
+static void handshake_failed(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ const char HANDSHAKE_FAILED_RESPONSE[] =
+ "HTTP/1.0 400 Bad Request\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ "WebSockets request was expected\r\n";
+ write_to_client(inspector, HANDSHAKE_FAILED_RESPONSE,
+ sizeof(HANDSHAKE_FAILED_RESPONSE) - 1);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ state->callback(inspector, kInspectorHandshakeFailed, state->path);
+}
+
+// init_handshake references message_complete_cb
+static void init_handshake(inspector_socket_t* inspector);
+
+static int message_complete_cb(http_parser* parser) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(parser->data);
+ struct http_parsing_state_s* state =
+ (struct http_parsing_state_s*) inspector->http_parsing_state;
+ if (parser->method != HTTP_GET) {
+ handshake_failed(inspector);
+ } else if (!parser->upgrade) {
+ if (state->callback(inspector, kInspectorHandshakeHttpGet, state->path)) {
+ init_handshake(inspector);
+ } else {
+ handshake_failed(inspector);
+ }
+ } else if (!state->ws_key) {
+ handshake_failed(inspector);
+ } else if (state->callback(inspector, kInspectorHandshakeUpgrading,
+ state->path)) {
+ char accept_string[ACCEPT_KEY_LENGTH + 1];
+ generate_accept_string(state->ws_key, accept_string);
+
+ const char accept_ws_prefix[] = "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: ";
+ const char accept_ws_suffix[] = "\r\n\r\n";
+ // Format has two chars (%s) that are replaced with actual key
+ char accept_response[sizeof(accept_ws_prefix) - 1 +
+ sizeof(accept_ws_suffix) - 1 +
+ ACCEPT_KEY_LENGTH];
+ memcpy(accept_response, accept_ws_prefix, sizeof(accept_ws_prefix) - 1);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1,
+ accept_string, ACCEPT_KEY_LENGTH);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1 + ACCEPT_KEY_LENGTH,
+ accept_ws_suffix, sizeof(accept_ws_suffix) - 1);
+ int len = sizeof(accept_response);
+ if (write_to_client(inspector, accept_response, len) >= 0) {
+ handshake_complete(inspector);
+ } else {
+ state->callback(inspector, kInspectorHandshakeFailed, nullptr);
+ close_connection(inspector);
+ }
+ inspector->http_parsing_state = nullptr;
+ } else {
+ handshake_failed(inspector);
+ }
+ return 0;
+}
+
+static void data_received_cb(uv_stream_s* client, ssize_t nread,
+ const uv_buf_t* buf) {
+#if DUMP_READS
+ if (nread >= 0) {
+ printf("%s (%ld bytes)\n", __FUNCTION__, nread);
+ dump_hex(buf->base, nread);
+ } else {
+ printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread));
+ }
+#endif
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>((client->data));
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->http_parsing_state->callback(inspector,
+ kInspectorHandshakeFailed,
+ nullptr);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ } else {
+ http_parser* parser = &state->parser;
+ ssize_t parsed = http_parser_execute(parser, &state->parser_settings,
+ inspector->buffer,
+ nread);
+ if (parsed < nread) {
+ handshake_failed(inspector);
+ }
+ inspector->data_len = 0;
+ }
+
+ if (inspector->http_parsing_state == nullptr) {
+ cleanup_http_parsing_state(state);
+ }
+}
+
+static void init_handshake(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ CHECK_NE(state, nullptr);
+ if (state->current_header) {
+ state->current_header[0] = '\0';
+ }
+ if (state->ws_key) {
+ state->ws_key[0] = '\0';
+ }
+ if (state->path) {
+ state->path[0] = '\0';
+ }
+ http_parser_init(&state->parser, HTTP_REQUEST);
+ state->parser.data = inspector;
+ http_parser_settings* settings = &state->parser_settings;
+ http_parser_settings_init(settings);
+ settings->on_header_field = header_field_cb;
+ settings->on_header_value = header_value_cb;
+ settings->on_message_complete = message_complete_cb;
+ settings->on_url = path_cb;
+}
+
+int inspector_accept(uv_stream_t* server, inspector_socket_t* inspector,
+ handshake_cb callback) {
+ ASSERT_NE(callback, nullptr);
+ // The only field that users should care about.
+ void* data = inspector->data;
+ memset(inspector, 0, sizeof(*inspector));
+ inspector->data = data;
+
+ inspector->http_parsing_state = (struct http_parsing_state_s*)
+ malloc(sizeof(struct http_parsing_state_s));
+ ASSERT_NE(nullptr, inspector->http_parsing_state);
+ memset(inspector->http_parsing_state, 0, sizeof(struct http_parsing_state_s));
+ uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ CHECK_NE(client, nullptr);
+ int err = uv_tcp_init(server->loop, &inspector->client);
+
+ if (err == 0) {
+ err = uv_accept(server, client);
+ }
+ if (err == 0) {
+ client->data = inspector;
+ init_handshake(inspector);
+ inspector->http_parsing_state->callback = callback;
+ err = uv_read_start(client, prepare_buffer,
+ data_received_cb);
+ }
+ if (err != 0) {
+ uv_close(reinterpret_cast<uv_handle_t*>(client), NULL);
+ }
+ return err;
+}
+
+void inspector_write(inspector_socket_t* inspector, const char* data,
+ size_t len) {
+ if (inspector->ws_mode) {
+ std::vector<char> output = encode_frame_hybi17(data, len);
+ write_to_client(inspector, &output[0], output.size());
+ } else {
+ write_to_client(inspector, data, len);
+ }
+}
+
+void inspector_close(inspector_socket_t* inspector,
+ inspector_cb callback) {
+ // libuv throws assertions when closing stream that's already closed - we
+ // need to do the same.
+ ASSERT(!uv_is_closing(reinterpret_cast<uv_handle_t*>(&inspector->client)));
+ ASSERT(!inspector->shutting_down);
+ inspector->shutting_down = true;
+ inspector->ws_state->close_cb = callback;
+ if (inspector->connection_eof) {
+ close_connection(inspector);
+ } else {
+ inspector_read_stop(inspector);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ inspector_read_start(inspector, nullptr, nullptr);
+ }
+}
+
+bool inspector_is_active(const struct inspector_socket_s* inspector) {
+ const uv_handle_t* client =
+ reinterpret_cast<const uv_handle_t*>(&inspector->client);
+ return !inspector->shutting_down && !uv_is_closing(client);
+}
diff --git a/src/inspector_socket.h b/src/inspector_socket.h
new file mode 100644
index 0000000000..3e52762e71
--- /dev/null
+++ b/src/inspector_socket.h
@@ -0,0 +1,57 @@
+#ifndef SRC_INSPECTOR_SOCKET_H_
+#define SRC_INSPECTOR_SOCKET_H_
+
+#include "http_parser.h"
+#include "uv.h"
+
+enum inspector_handshake_event {
+ kInspectorHandshakeUpgrading,
+ kInspectorHandshakeUpgraded,
+ kInspectorHandshakeHttpGet,
+ kInspectorHandshakeFailed
+};
+
+struct inspector_socket_s;
+
+typedef void (*inspector_cb)(struct inspector_socket_s*, int);
+// Notifies as handshake is progressing. Returning false as a response to
+// kInspectorHandshakeUpgrading or kInspectorHandshakeHttpGet event will abort
+// the connection. inspector_write can be used from the callback.
+typedef bool (*handshake_cb)(struct inspector_socket_s*,
+ enum inspector_handshake_event state,
+ const char* path);
+
+struct http_parsing_state_s;
+struct ws_state_s;
+
+struct inspector_socket_s {
+ void* data;
+ struct http_parsing_state_s* http_parsing_state;
+ struct ws_state_s* ws_state;
+ char* buffer;
+ size_t buffer_size;
+ size_t data_len;
+ size_t last_read_end;
+ uv_tcp_t client;
+ bool ws_mode;
+ bool shutting_down;
+ bool connection_eof;
+};
+
+typedef struct inspector_socket_s inspector_socket_t;
+
+int inspector_accept(uv_stream_t* server, struct inspector_socket_s* inspector,
+ handshake_cb callback);
+
+void inspector_close(struct inspector_socket_s* inspector,
+ inspector_cb callback);
+
+// Callbacks will receive handles that has inspector in data field...
+int inspector_read_start(struct inspector_socket_s* inspector, uv_alloc_cb,
+ uv_read_cb);
+void inspector_read_stop(struct inspector_socket_s* inspector);
+void inspector_write(struct inspector_socket_s* inspector,
+ const char* data, size_t len);
+bool inspector_is_active(const struct inspector_socket_s* inspector);
+
+#endif // SRC_INSPECTOR_SOCKET_H_
diff --git a/src/node.cc b/src/node.cc
index cda2bac096..258ebb596a 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -137,6 +137,9 @@ static bool track_heap_objects = false;
static const char* eval_string = nullptr;
static unsigned int preload_module_count = 0;
static const char** preload_modules = nullptr;
+#if HAVE_INSPECTOR
+static bool use_inspector = false;
+#endif
static bool use_debug_agent = false;
static bool debug_wait_connect = false;
static int debug_port = 5858;
@@ -3412,6 +3415,22 @@ static bool ParseDebugOpt(const char* arg) {
port = arg + sizeof("--debug-brk=") - 1;
} else if (!strncmp(arg, "--debug-port=", sizeof("--debug-port=") - 1)) {
port = arg + sizeof("--debug-port=") - 1;
+#if HAVE_INSPECTOR
+ // Specifying both --inspect and --debug means debugging is on, using Chromium
+ // inspector.
+ } else if (!strcmp(arg, "--inspect")) {
+ use_debug_agent = true;
+ use_inspector = true;
+ } else if (!strncmp(arg, "--inspect=", sizeof("--inspect=") - 1)) {
+ use_debug_agent = true;
+ use_inspector = true;
+ port = arg + sizeof("--inspect=") - 1;
+#else
+ } else if (!strncmp(arg, "--inspect", sizeof("--inspect") - 1)) {
+ fprintf(stderr,
+ "Inspector support is not available with this Node.js build\n");
+ return false;
+#endif
} else {
return false;
}
@@ -3682,10 +3701,19 @@ static void DispatchMessagesDebugAgentCallback(Environment* env) {
static void StartDebug(Environment* env, bool wait) {
CHECK(!debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Start(default_platform, debug_port, wait);
+ debugger_running = true;
+ } else {
+#endif
+ env->debugger_agent()->set_dispatch_handler(
+ DispatchMessagesDebugAgentCallback);
+ debugger_running = env->debugger_agent()->Start(debug_port, wait);
+#if HAVE_INSPECTOR
+ }
+#endif
- env->debugger_agent()->set_dispatch_handler(
- DispatchMessagesDebugAgentCallback);
- debugger_running = env->debugger_agent()->Start(debug_port, wait);
if (debugger_running == false) {
fprintf(stderr, "Starting debugger on port %d failed\n", debug_port);
fflush(stderr);
@@ -3697,6 +3725,11 @@ static void StartDebug(Environment* env, bool wait) {
// Called from the main thread.
static void EnableDebug(Environment* env) {
CHECK(debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ return;
+ }
+#endif
// Send message to enable debug in workers
HandleScope handle_scope(env->isolate());
@@ -3991,7 +4024,15 @@ static void DebugPause(const FunctionCallbackInfo<Value>& args) {
static void DebugEnd(const FunctionCallbackInfo<Value>& args) {
if (debugger_running) {
Environment* env = Environment::GetCurrent(args);
- env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Stop();
+ } else {
+#endif
+ env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ }
+#endif
debugger_running = false;
}
}
@@ -4420,6 +4461,24 @@ static void StartNodeInstance(void* arg) {
instance_data->set_exit_code(exit_code);
RunAtExit(env);
+#if HAVE_INSPECTOR
+ if (env->inspector_agent()->connected()) {
+ // Restore signal dispositions, the app is done and is no longer
+ // capable of handling signals.
+#ifdef __POSIX__
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ for (unsigned nr = 1; nr < 32; nr += 1) {
+ if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF)
+ continue;
+ act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL;
+ CHECK_EQ(0, sigaction(nr, &act, nullptr));
+ }
+#endif
+ env->inspector_agent()->WaitForDisconnect();
+ }
+#endif
+
#if defined(LEAK_SANITIZER)
__lsan_do_leak_check();
#endif
diff --git a/src/node_internals.h b/src/node_internals.h
index 2875f5ac79..64134d9ab8 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -221,7 +221,7 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
// by clearing all callbacks that could handle the error.
void ClearFatalExceptionHandlers(Environment* env);
-enum NodeInstanceType { MAIN, WORKER };
+enum NodeInstanceType { MAIN, WORKER, REMOTE_DEBUG_SERVER };
class NodeInstanceData {
public:
@@ -265,6 +265,10 @@ class NodeInstanceData {
return node_instance_type_ == WORKER;
}
+ bool is_remote_debug_server() {
+ return node_instance_type_ == REMOTE_DEBUG_SERVER;
+ }
+
int argc() {
return argc_;
}
diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc
index 3ee0251f9b..8d31dbf623 100644
--- a/src/signal_wrap.cc
+++ b/src/signal_wrap.cc
@@ -65,6 +65,15 @@ class SignalWrap : public HandleWrap {
SignalWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
int signum = args[0]->Int32Value();
+#if defined(__POSIX__) && defined(HAVE_INSPECTOR)
+ if (signum == SIGPROF) {
+ Environment* env = Environment::GetCurrent(args);
+ if (env->inspector_agent()->IsStarted()) {
+ fprintf(stderr, "process.on(SIGPROF) is reserved while debugging\n");
+ return;
+ }
+ }
+#endif
int err = uv_signal_start(&wrap->handle_, OnSignal, signum);
args.GetReturnValue().Set(err);
}