Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

src: debug agent, work in progress

  • Loading branch information...
commit 0737c3bcc9a482a4ea3bbd6671a04eec9e0e75e1 1 parent 2e67740
@bnoordhuis authored
View
15 node.gyp
@@ -87,8 +87,10 @@
],
'sources': [
- 'src/fs_event_wrap.cc',
'src/cares_wrap.cc',
+ 'src/debugger.cc',
+ 'src/debugger.h',
+ 'src/fs_event_wrap.cc',
'src/handle_wrap.cc',
'src/node.cc',
'src/node_buffer.cc',
@@ -435,6 +437,17 @@
'<@(_inputs)',
],
},
+ {
+ 'action_name': 'node_js2c_debug_agent',
+ 'inputs': [ 'src/debugger.js' ],
+ 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/debugger-js.h' ],
+ 'action': [
+ '<(python)',
+ 'tools/js2c.py',
+ '<@(_outputs)',
+ '<@(_inputs)',
+ ],
+ },
],
}, # end node_js2c
{
View
512 src/debugger.cc
@@ -0,0 +1,512 @@
+// Copyright 2014, StrongLoop, Inc. <callback@strongloop.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "debugger.h"
+#include "debugger-js.h" // Generated from src/debugger.js by js2c.
+#include "env.h"
+#include "env-inl.h"
+#include "node_version.h"
+#include "queue.h"
+#include "util.h"
+#include "util-inl.h"
+#include "uv.h"
+#include "v8-debug.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace node {
+
+using v8::Break;
+using v8::Context;
+using v8::Debug;
+using v8::DebugEvent;
+using v8::External;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Isolate;
+using v8::Local;
+using v8::Locker;
+using v8::Null;
+using v8::Object;
+using v8::Persistent;
+using v8::Script;
+using v8::String;
+using v8::TryCatch;
+using v8::V8;
+using v8::Value;
+
+// GlobalDebugger drives the debug thread. It has its own event loop.
+class GlobalDebugger {
+ public:
+ static const char* SendAndReceive(const char* command);
+ private:
+ static const char* channel;
+ static uv_once_t call_once;
+ static uv_thread_t debug_thread;
+ static uv_mutex_t mutex;
+ static uv_cond_t condvar;
+ static uv_loop_t* event_loop;
+ static uv_async_t async_handle;
+ static Isolate* isolate;
+ static void CreateDebugThread();
+ static void DebugThreadMain(void*);
+ static void OnAlloc(uv_handle_t* handle, size_t size, uv_buf_t* buf);
+ static void OnAsync(uv_async_t* handle);
+ static void OnConnection(uv_stream_t* handle, int err);
+ static void OnRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf);
+ static void OnWrite(uv_write_t* req, int err);
+ static void AcceptFunction(const FunctionCallbackInfo<Value>& info);
+ static void BindFunction(const FunctionCallbackInfo<Value>& info);
+ static void CloseFunction(const FunctionCallbackInfo<Value>& info);
+ static void ListenFunction(const FunctionCallbackInfo<Value>& info);
+ static void OneByteToUtf8Function(const FunctionCallbackInfo<Value>& info);
+ static void PrintFunction(const FunctionCallbackInfo<Value>& info);
+ static void ReadStartFunction(const FunctionCallbackInfo<Value>& info);
+ static void ReadStopFunction(const FunctionCallbackInfo<Value>& info);
+ static void SocketFunction(const FunctionCallbackInfo<Value>& info);
+ static void StrerrorFunction(const FunctionCallbackInfo<Value>& info);
+ static void WriteFunction(const FunctionCallbackInfo<Value>& info);
+ static Local<Value> Call(const char* name, int argc, Local<Value> argv[]);
+ GlobalDebugger(); // Prevent instantiation.
+};
+
+template <typename T>
+inline uint32_t ident(const T* that) {
+ return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(that->data));
+}
+
+template <typename T>
+inline Local<Value> ident(Isolate* isolate, const T* that) {
+ return Integer::NewFromUnsigned(isolate, ident(that));
+}
+
+template <typename T>
+inline void set_ident(T* that, uint32_t value) {
+ that->data = reinterpret_cast<void*>(static_cast<uintptr_t>(value));
+}
+
+const char* GlobalDebugger::channel;
+uv_once_t GlobalDebugger::call_once;
+uv_thread_t GlobalDebugger::debug_thread;
+uv_loop_t* GlobalDebugger::event_loop;
+uv_async_t GlobalDebugger::async_handle;
+uv_mutex_t GlobalDebugger::mutex;
+uv_cond_t GlobalDebugger::condvar;
+Isolate* GlobalDebugger::isolate;
+
+const char* GlobalDebugger::SendAndReceive(const char* command) {
+ ::uv_once(&call_once, CreateDebugThread);
+ ::uv_mutex_lock(&mutex);
+ CHECK_EQ(NULL, channel);
+ channel = command;
+ CHECK_EQ(0, ::uv_async_send(&async_handle));
+ do {
+ ::uv_cond_wait(&condvar, &mutex);
+ } while (channel == command);
+ const char* const answer = channel;
+ channel = NULL;
+ ::uv_mutex_unlock(&mutex);
+ return answer;
+}
+
+void GlobalDebugger::CreateDebugThread() {
+ CHECK_EQ(0, ::uv_cond_init(&condvar));
+ CHECK_EQ(0, ::uv_mutex_init(&mutex));
+ ::uv_mutex_lock(&mutex);
+ CHECK_EQ(0, ::uv_thread_create(&debug_thread, DebugThreadMain, 0));
+ do {
+ ::uv_cond_wait(&condvar, &mutex);
+ } while (isolate == NULL); // Wait for debug thread's ready signal.
+ ::uv_mutex_unlock(&mutex);
+}
+
+void GlobalDebugger::DebugThreadMain(void*) {
+ ::uv_mutex_lock(&mutex);
+ event_loop = ::uv_loop_new();
+ CHECK_NE(NULL, event_loop);
+ CHECK_EQ(0, ::uv_async_init(event_loop, &async_handle, OnAsync));
+ isolate = Isolate::New();
+ CHECK_NE(NULL, isolate);
+ {
+ Locker locker(isolate);
+ Isolate::Scope isolate_scope(isolate);
+ HandleScope handle_scope(isolate);
+ Local<Context> context = Context::New(isolate);
+ Context::Scope context_scope(context);
+ Local<String> script_source = String::NewFromUtf8(isolate, debugger_native);
+ Local<Script> script = Script::Compile(script_source);
+ CHECK_EQ(false, script.IsEmpty()); // Exception.
+ Local<Value> return_value = script->Run();
+ CHECK_EQ(false, return_value.IsEmpty()); // Exception.
+ CHECK_EQ(true, return_value->IsFunction());
+ Local<Function> entry_point = return_value.As<Function>();
+ Local<Object> bindings = Object::New(isolate);
+ bindings->Set(
+ String::NewFromUtf8(isolate, "NODE_VERSION_STRING"),
+ String::NewFromUtf8(isolate, NODE_VERSION_STRING));
+ bindings->Set(
+ String::NewFromUtf8(isolate, "V8_VERSION_STRING"),
+ String::NewFromUtf8(isolate, V8::GetVersion()));
+ bindings->Set(
+ String::NewFromUtf8(isolate, "UV_EOF"),
+ Integer::New(isolate, UV_EOF));
+ bindings->Set(
+ String::NewFromUtf8(isolate, "stdout"),
+ External::New(isolate, stdout));
+ bindings->Set(
+ String::NewFromUtf8(isolate, "stderr"),
+ External::New(isolate, stderr));
+ bindings->Set(
+ String::NewFromUtf8(isolate, "accept"),
+ FunctionTemplate::New(isolate, AcceptFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "bind"),
+ FunctionTemplate::New(isolate, BindFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "close"),
+ FunctionTemplate::New(isolate, CloseFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "listen"),
+ FunctionTemplate::New(isolate, ListenFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "oneByteToUtf8"),
+ FunctionTemplate::New(isolate, OneByteToUtf8Function)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "print"),
+ FunctionTemplate::New(isolate, PrintFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "readStart"),
+ FunctionTemplate::New(isolate, ReadStartFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "readStop"),
+ FunctionTemplate::New(isolate, ReadStopFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "socket"),
+ FunctionTemplate::New(isolate, SocketFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "strerror"),
+ FunctionTemplate::New(isolate, StrerrorFunction)->GetFunction());
+ bindings->Set(
+ String::NewFromUtf8(isolate, "write"),
+ FunctionTemplate::New(isolate, WriteFunction)->GetFunction());
+ Local<Value> argv[] = { bindings };
+ return_value = entry_point->Call(context->Global(), ARRAY_SIZE(argv), argv);
+ CHECK_EQ(false, return_value.IsEmpty()); // Exception.
+ ::uv_cond_signal(&condvar);
+ ::uv_mutex_unlock(&mutex);
+ CHECK_EQ(0, ::uv_run(event_loop, UV_RUN_DEFAULT));
+ ::uv_mutex_lock(&mutex);
+ }
+ isolate->Dispose();
+ isolate = NULL;
+ ::uv_close(reinterpret_cast<uv_handle_t*>(&async_handle), NULL);
+ CHECK_EQ(0, ::uv_run(event_loop, UV_RUN_DEFAULT));
+ ::uv_loop_delete(event_loop);
+ event_loop = NULL;
+ ::uv_mutex_unlock(&mutex);
+}
+
+Local<Value> GlobalDebugger::Call(const char* name,
+ int argc,
+ Local<Value> argv[]) {
+ Local<Context> context = isolate->GetCurrentContext();
+ CHECK_EQ(false, context.IsEmpty());
+ Local<Object> global_object = context->Global();
+ Local<String> key = String::NewFromUtf8(isolate, name);
+ Local<Value> value = global_object->Get(key);
+ CHECK_EQ(true, value->IsFunction());
+ Local<Function> callback = value.As<Function>();
+ TryCatch try_catch;
+ Local<Value> return_value = callback->Call(Null(isolate), argc, argv);
+ if (try_catch.HasCaught()) {
+ String::Utf8Value exception(try_catch.Exception());
+ String::Utf8Value stack_trace(try_catch.StackTrace());
+ ::fprintf(stderr, "Exception in debug agent: %s\n%s\n",
+ *exception, *stack_trace);
+ ::fflush(stderr);
+ ::abort();
+ }
+ return return_value;
+}
+
+void GlobalDebugger::OnAsync(uv_async_t* handle) {
+ CHECK_EQ(handle, &async_handle);
+ ::uv_mutex_lock(&mutex);
+ CHECK_NE(NULL, channel);
+ Isolate::Scope isolate_scope(isolate);
+ HandleScope handle_scope(isolate);
+ Local<String> command = String::NewFromUtf8(isolate, channel);
+ Local<Value> argv[] = { command };
+ Local<Value> return_value = Call("onasync", ARRAY_SIZE(argv), argv);
+ CHECK_EQ(false, return_value.IsEmpty()); // Exception.
+ String::Utf8Value string_value(return_value);
+ const size_t size = string_value.length() + 1;
+ char* const answer = new char[size];
+ ::memcpy(answer, *string_value, size);
+ channel = answer;
+ ::uv_cond_signal(&condvar);
+ ::uv_mutex_unlock(&mutex);
+}
+
+void GlobalDebugger::OnAlloc(uv_handle_t*, size_t size, uv_buf_t* buf) {
+ buf->base = new char[size];
+ buf->len = size;
+}
+
+void GlobalDebugger::OnConnection(uv_stream_t* handle, int err) {
+ // XXX(bnoordhuis) Only plausible error at this time is hitting the open
+ // file descriptor limit. I don't think it makes sense to go on in that
+ // case but on the other hand, killing the application is pretty drastic.
+ CHECK_EQ(0, err);
+ Isolate::Scope isolate_scope(isolate);
+ HandleScope handle_scope(isolate);
+ Local<Value> arg = ident(isolate, handle);
+ Local<Value> return_value = Call("onconnection", 1, &arg);
+ CHECK_EQ(false, return_value.IsEmpty()); // Exception.
+}
+
+void GlobalDebugger::OnRead(uv_stream_t* handle,
+ ssize_t nread,
+ const uv_buf_t* buf) {
+ Isolate::Scope isolate_scope(isolate);
+ HandleScope handle_scope(isolate);
+ char* const data = buf->base;
+ Local<Value> string = Null(isolate);
+ if (nread > 0) {
+ // Note: the one-byte encoding mangles multi-byte character sequences but
+ // we JS land fixes that up later by recoding the string to UTF-8 after
+ // initial processing is complete. It's not terribly efficient but this
+ // is not performance-critical code and it means we don't have to deal
+ // with multi-byte characters that are split across reads.
+ string = String::NewFromOneByte(isolate, reinterpret_cast<uint8_t*>(data),
+ String::kNormalString, nread);
+ }
+ Local<Value> argv[] = {
+ ident(isolate, handle),
+ Integer::New(isolate, nread),
+ string
+ };
+ Call("onread", ARRAY_SIZE(argv), argv);
+ delete[] data;
+}
+
+void GlobalDebugger::OnWrite(uv_write_t* req, int err) {
+ uv_stream_t* const handle = req->handle;
+ delete reinterpret_cast<char*>(req);
+ Isolate::Scope isolate_scope(isolate);
+ HandleScope handle_scope(isolate);
+ Local<Value> argv[] = {
+ ident(isolate, handle),
+ Integer::New(isolate, err)
+ };
+ Call("onwrite", ARRAY_SIZE(argv), argv);
+}
+
+void GlobalDebugger::AcceptFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ CHECK_EQ(true, info[1]->IsExternal());
+ uv_stream_t* const server_handle =
+ static_cast<uv_stream_t*>(info[0].As<External>()->Value());
+ uv_stream_t* const client_handle =
+ static_cast<uv_stream_t*>(info[1].As<External>()->Value());
+ const int err = ::uv_accept(server_handle, client_handle);
+ info.GetReturnValue().Set(err);
+}
+
+void GlobalDebugger::BindFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ CHECK_EQ(true, info[1]->IsString());
+ CHECK_EQ(true, info[2]->IsUint32());
+ uv_tcp_t* const handle =
+ static_cast<uv_tcp_t*>(info[0].As<External>()->Value());
+ String::Utf8Value host(info[1]);
+ const uint32_t port = info[2]->Uint32Value();
+ CHECK(port < 65536);
+ STATIC_ASSERT(sizeof(sockaddr_in) <= sizeof(sockaddr_in6));
+ char address[sizeof(sockaddr_in6)];
+ int err = ::uv_ip4_addr(*host, port, reinterpret_cast<sockaddr_in*>(address));
+ if (err != 0) {
+ err = ::uv_ip6_addr(*host, port, reinterpret_cast<sockaddr_in6*>(address));
+ }
+ if (err == 0) {
+ err = ::uv_tcp_bind(handle, reinterpret_cast<const sockaddr*>(&address), 0);
+ }
+ info.GetReturnValue().Set(err);
+}
+
+void GlobalDebugger::CloseFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ uv_handle_t* const handle =
+ static_cast<uv_handle_t*>(info[0].As<External>()->Value());
+ ::uv_close(handle, NULL);
+}
+
+void GlobalDebugger::ListenFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ CHECK_EQ(true, info[1]->IsInt32());
+ uv_stream_t* const handle =
+ static_cast<uv_stream_t*>(info[0].As<External>()->Value());
+ const int32_t backlog = info[1]->Int32Value();
+ const int err = ::uv_listen(handle, backlog, OnConnection);
+ info.GetReturnValue().Set(err);
+}
+
+void GlobalDebugger::OneByteToUtf8Function(
+ const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsString());
+ String::Value string_value(info[0]);
+ const size_t size = string_value.length();
+ uint8_t* const data = new uint8_t[size];
+ for (size_t n = 0; n < size; n += 1) {
+ data[n] = static_cast<uint8_t>(255 & (*string_value)[n]);
+ }
+ Local<String> s = String::NewFromUtf8(isolate, reinterpret_cast<char*>(data),
+ String::kNormalString, size);
+ delete[] data;
+ info.GetReturnValue().Set(s);
+}
+
+void GlobalDebugger::PrintFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ FILE* stream = static_cast<FILE*>(info[0].As<External>()->Value());
+ for (int i = 1, n = info.Length(); i < n; i += 1) {
+ String::Utf8Value string_value(info[i]);
+ if (*string_value != NULL) {
+ ::fwrite(*string_value, 1, string_value.length(), stream);
+ }
+ }
+ ::fwrite("\n", 1, 1, stream);
+ ::fflush(stream);
+}
+
+void GlobalDebugger::ReadStartFunction(
+ const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ uv_stream_t* const handle =
+ static_cast<uv_stream_t*>(info[0].As<External>()->Value());
+ CHECK_EQ(0, ::uv_read_start(handle, OnAlloc, OnRead));
+}
+
+void GlobalDebugger::ReadStopFunction(
+ const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ uv_stream_t* const handle =
+ static_cast<uv_stream_t*>(info[0].As<External>()->Value());
+ CHECK_EQ(0, ::uv_read_stop(handle));
+}
+
+void GlobalDebugger::SocketFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsUint32());
+ const uint32_t sid = info[0]->Uint32Value();
+ uv_tcp_t* const handle = new uv_tcp_t;
+ CHECK_EQ(0, ::uv_tcp_init(event_loop, handle));
+ set_ident(handle, sid);
+ info.GetReturnValue().Set(External::New(isolate, handle));
+}
+
+void GlobalDebugger::StrerrorFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsInt32());
+ const int32_t err = info[0]->Int32Value();
+ const char* errmsg = ::uv_strerror(err);
+ Local<String> string = String::NewFromUtf8(isolate, errmsg);
+ info.GetReturnValue().Set(string);
+}
+
+void GlobalDebugger::WriteFunction(const FunctionCallbackInfo<Value>& info) {
+ Isolate* const isolate = info.GetIsolate();
+ HandleScope handle_scope(isolate);
+ CHECK_EQ(true, info[0]->IsExternal());
+ CHECK_EQ(true, info[1]->IsString());
+ uv_stream_t* const handle =
+ static_cast<uv_stream_t*>(info[0].As<External>()->Value());
+ String::Utf8Value string_value(info[1]);
+ const size_t size = string_value.length();
+ char* const raw_data = new char[sizeof(uv_write_t) + size];
+ uv_write_t* const req = reinterpret_cast<uv_write_t*>(raw_data);
+ char* const data = raw_data + sizeof(*req);
+ ::memcpy(data, *string_value, size);
+ uv_buf_t buf;
+ buf.base = data;
+ buf.len = size;
+ CHECK_EQ(0, ::uv_write(req, handle, &buf, 1, OnWrite));
+}
+
+void MessageHandler(const Debug::Message& message) {
+ if (message.GetEvent() != Break) return; // Uninteresting event.
+}
+
+// Break() can be called from a signal handler and may therefore only call
+// async signal-safe functions. That also means that it's not allowed to
+// grab the mutex because doing so will deadlock when the thread already
+// holds the lock. (And besides, the pthread functions are not guaranteed
+// to be signal-safe, only sem_post() is.)
+void Debugger::Break(Isolate* isolate) {
+ // Can't call Debug::DebugBreakForCommand() here, it allocates memory.
+ Debug::DebugBreak(isolate);
+}
+
+Debugger::Debugger(Isolate* isolate) {
+ // SetDebugEventListener() and SetMessageHandler() are isolate-ified but
+ // their API doesn't reflect that; make sure we've entered the isolate.
+ Isolate::Scope isolate_scope(isolate);
+ Debug::SetMessageHandler(MessageHandler);
+}
+
+int Debugger::Start() {
+ const char* answer =
+ GlobalDebugger::SendAndReceive("{\"cmd\":\"startDebugger\"}");
+ return 0;
+}
+
+unsigned short Debugger::port() const {
+ return port_;
+}
+
+void Debugger::set_port(unsigned short value) {
+ port_ = value;
+}
+
+} // namespace node
View
47 src/debugger.h
@@ -0,0 +1,47 @@
+// Copyright 2014, StrongLoop, Inc. <callback@strongloop.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef SRC_DEBUGGER_H_
+#define SRC_DEBUGGER_H_
+
+#include "util.h"
+#include "v8.h"
+
+namespace node {
+
+class Debugger {
+ public:
+ static void Break(v8::Isolate* isolate); // Async signal-safe.
+ void set_port(unsigned short value);
+ unsigned short port() const;
+ int Start();
+ // Only call when you're Environment::IsolateData::IsolateData().
+ // Can't be private with IsolateData as a friend because of
+ // a circular dependency between debug-agent.h and env.h.
+ explicit Debugger(v8::Isolate* isolate);
+ private:
+ unsigned short port_;
+ DISALLOW_COPY_AND_ASSIGN(Debugger);
+};
+
+} // namespace node
+
+#endif // SRC_DEBUGGER_H_
View
212 src/debugger.js
@@ -0,0 +1,212 @@
+// Copyright 2014, StrongLoop, Inc. <callback@strongloop.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;(function(B) {
+ 'use strict';
+
+ var kDefaultHost = '127.0.0.1';
+ var kDefaultPort = 5858;
+
+ function CHECK(ok, message) {
+ if (!ok) throw Error(message || 'Assertion failed.');
+ }
+
+ function UNREACHABLE() {
+ CHECK(false, 'Unreachable location reached.');
+ }
+
+ var info = B.print.call.bind(B.print, B, B.stdout, '[debug agent] ');
+ var warn = B.print.call.bind(B.print, B, B.stderr, '[debug agent] ');
+
+ function Parser() {
+ this.cont_ = Parser.make();
+ }
+
+ Parser.prototype = {
+ parse: function(data) {
+ this.cont_ = this.cont_(this, data);
+ },
+ onheaders: function(names, values) {
+ print('names:', names);
+ print('values:', values);
+ },
+ onbody: function(body) {
+ print('body:', body);
+ },
+ onerror: function(errmsg) {
+ print('errmsg:', errmsg);
+ },
+ };
+
+ Parser.make = function() {
+ return headers; // Start state.
+
+ var index = 0;
+ var input = '';
+ var length = 0;
+
+ function headers(that, data) {
+ input += data;
+ var slice = input.slice(index);
+ var match = slice.match(/\r?\n\r?\n/); // Find end of headers.
+ if (match === null) {
+ index = input.length;
+ return headers; // Need more input.
+ }
+ index += match.index;
+ var fields = input.slice(0, index);
+ input = input.slice(index + match[0].length); // Body.
+ index = 0;
+ // Split at (and consume) \r\n unless next line has leading whitespace.
+ fields = fields.split(/\r?\n(?:(?=[^\t\r\n ]))/);
+ fields = fields.map(function(s) { return s.split(/:/, 2); });
+ var flattened = [].concat.apply([], fields); // flatMap
+ if (fields.length !== 2 * flattened.length) {
+ return error(that); // Failed split(/:/).
+ }
+ fields = flattened, flattened = null;
+ fields = fields.map(function(s) { return s.trim(); });
+ var names = fields.filter(function(_, i) { return i % 2 === 0; });
+ var values = fields.filter(function(_, i) { return i % 2 !== 0; });
+ that.onheaders(names, values);
+ var n = fields.reduce(function(a, s, i) {
+ return a === -1 && /^Content-Length$/i.test(s) ? i : a;
+ }, -1);
+ length = values[n] | 0;
+ return body('');
+ }
+
+ function body(that, data) {
+ input += data;
+ if (input.length < length) {
+ return body; // Need more input.
+ }
+ var s = input.slice(0, length);
+ input = input.slice(length); // Headers of next request.
+ s = B.oneByteToUtf8(s); // Fix up multi-byte sequences.
+ that.onbody(s);
+ return headers('');
+ }
+
+ function error(that) {
+ that.onerror('Parse error');
+ return error; // End state.
+ }
+ };
+
+ function Handle() {
+ this.id_ = ++Handle.ids;
+ this.handle_ = B.socket(this.id_);
+ this.parser_ = null;
+ Handle.map[this.id_] = this;
+ }
+ Handle.ids = 0;
+ Handle.map = {};
+
+ Handle.get = function(id) {
+ return Handle.map[id];
+ };
+
+ Handle.prototype = {
+ close: function() {
+ if (this.handle_ === null) return;
+ delete Handle.map[this.id_];
+ B.close(this.handle_);
+ this.handle_ = null;
+ },
+ handle: function() {
+ return this.handle_;
+ },
+ id: function() {
+ return this.id_;
+ },
+ parser: function() {
+ return this.parser_ || (this.parser_ = new Parser());
+ },
+ };
+
+ this.onasync = function(ev) {
+ ev = JSON.parse(ev);
+ switch (ev.cmd) {
+ case 'startDebugger':
+ return startDebugger(ev);
+ }
+ UNREACHABLE();
+ };
+
+ this.onconnection = function(ident) {
+ var server = Handle.get(ident);
+ var client = new Handle();
+ var err = B.accept(server.handle(), client.handle());
+ if (err !== 0) {
+ warn('accept error: ', B.strerror(err));
+ client.close();
+ return;
+ }
+ var headers =
+ 'Type: connect\r\n' +
+ 'V8-Version: ' + B.V8_VERSION_STRING + '\r\n' +
+ 'Protocol-Version: 1\r\n' +
+ 'Embedding-Host: node ' + B.NODE_VERSION_STRING + '\r\n' +
+ 'Content-Length: 0\r\n' +
+ '\r\n';
+ B.write(client.handle(), headers);
+ };
+
+ // Note: |data| is a one-byte encoded string. Any multi-byte characters
+ // are mangled at this point but we fix that up when we have the full body.
+ // Headers don't need a post-processing step because they are ASCII-only.
+ this.onread = function(ident, err, data) {
+ var client = Handle.get(ident);
+ if (err < 0) {
+ if (err !== B.UV_EOF) warn('read error: ', B.strerror(err));
+ client.close();
+ return;
+ }
+ client.parser().parse(data);
+ };
+
+ this.onwrite = function(ident, err) {
+ var client = Handle.get(ident);
+ if (err === 0) {
+ B.readStart(client.handle());
+ } else {
+ warn('write error: ', B.strerror(err));
+ client.close();
+ }
+ };
+
+ function startDebugger(ev) {
+ var server = new Handle();
+ var host = ev.host || kDefaultHost;
+ var port = ev.port || kDefaultPort;
+ var err = B.bind(server.handle(), host, port);
+ if (err === 0) {
+ err = B.listen(server.handle(), /* backlog */ 1);
+ }
+ if (err === 0) {
+ info('listening on ', host, ':', port);
+ } else {
+ warn('bind error: ', host, ':', port, ': ', B.strerror(err));
+ server.close();
+ }
+ }
+});
View
10 src/env-inl.h
@@ -94,6 +94,7 @@ inline void Environment::IsolateData::Put() {
inline Environment::IsolateData::IsolateData(v8::Isolate* isolate)
: event_loop_(uv_default_loop()),
isolate_(isolate),
+ debugger_(isolate),
#define V(PropertyName, StringValue) \
PropertyName ## _(isolate, FIXED_ONE_BYTE_STRING(isolate, StringValue)),
PER_ISOLATE_STRING_PROPERTIES(V)
@@ -110,6 +111,11 @@ inline v8::Isolate* Environment::IsolateData::isolate() const {
return isolate_;
}
+inline Debugger* Environment::IsolateData::debugger() const {
+ // The const_cast is okay, it doesn't violate conceptual const-ness.
+ return const_cast<Debugger*>(&debugger_);
+}
+
inline Environment::AsyncListener::AsyncListener() {
for (int i = 0; i < kFieldsCount; ++i)
fields_[i] = 0;
@@ -258,6 +264,10 @@ inline v8::Isolate* Environment::isolate() const {
return isolate_;
}
+inline Debugger* Environment::debugger() const {
+ return isolate_data()->debugger();
+}
+
inline bool Environment::has_async_listener() const {
// The const_cast is okay, it doesn't violate conceptual const-ness.
return const_cast<Environment*>(this)->async_listener()->has_listener();
View
5 src/env.h
@@ -23,6 +23,7 @@
#define SRC_ENV_H_
#include "ares.h"
+#include "debugger.h"
#include "tree.h"
#include "util.h"
#include "uv.h"
@@ -366,6 +367,8 @@ class Environment {
inline v8::Isolate* isolate() const;
inline uv_loop_t* event_loop() const;
+ inline Debugger* debugger() const; // Per-isolate property.
+
inline bool has_async_listener() const;
inline bool in_domain() const;
inline uint32_t watched_providers() const;
@@ -492,6 +495,7 @@ class Environment {
static inline IsolateData* GetOrCreate(v8::Isolate* isolate);
inline void Put();
inline uv_loop_t* event_loop() const;
+ inline Debugger* debugger() const;
// Defined in src/node_profiler.cc.
void StartGarbageCollectionTracking(Environment* env);
@@ -519,6 +523,7 @@ class Environment {
uv_loop_t* const event_loop_;
v8::Isolate* const isolate_;
+ Debugger debugger_;
#define V(PropertyName, StringValue) \
v8::Eternal<v8::String> PropertyName ## _;
View
93 src/node.cc
@@ -42,6 +42,7 @@
#include "ares.h"
#include "async-wrap.h"
#include "async-wrap-inl.h"
+#include "debugger.h"
#include "env.h"
#include "env-inl.h"
#include "handle_wrap.h"
@@ -141,7 +142,6 @@ bool no_deprecation = false;
// process-relative uptime base, initialized at start-up
static double prog_start_time;
static bool debugger_running;
-static uv_async_t dispatch_debug_messages_async;
static Isolate* node_isolate = NULL;
@@ -2481,18 +2481,20 @@ static Handle<Object> GetFeatures(Environment* env) {
static void DebugPortGetter(Local<String> property,
const PropertyCallbackInfo<Value>& info) {
- Environment* env = Environment::GetCurrent(info.GetIsolate());
- HandleScope scope(env->isolate());
- info.GetReturnValue().Set(debug_port);
+ Isolate* isolate = info.GetIsolate();
+ HandleScope scope(isolate);
+ Environment* env = Environment::GetCurrent(isolate);
+ info.GetReturnValue().Set(env->debugger()->port());
}
static void DebugPortSetter(Local<String> property,
Local<Value> value,
const PropertyCallbackInfo<void>& info) {
- Environment* env = Environment::GetCurrent(info.GetIsolate());
- HandleScope scope(env->isolate());
- debug_port = value->NumberValue();
+ Isolate* isolate = info.GetIsolate();
+ HandleScope scope(isolate);
+ Environment* env = Environment::GetCurrent(isolate);
+ env->debugger()->set_port(value->Uint32Value());
}
@@ -3075,32 +3077,18 @@ static void ParseArgs(int* argc,
}
-// Called from V8 Debug Agent TCP thread.
-static void DispatchMessagesDebugAgentCallback() {
- uv_async_send(&dispatch_debug_messages_async);
-}
-
-
// Called from the main thread.
-static void EnableDebug(Isolate* isolate, bool wait_connect) {
- assert(debugger_running == false);
- Isolate::Scope isolate_scope(isolate);
- HandleScope handle_scope(isolate);
+static void EnableDebug(Environment* env, bool wait_connect) {
+ CHECK_EQ(false, debugger_running);
+ HandleScope handle_scope(env->isolate());
+ const int errorno = env->debugger()->Start();
+ debugger_running = (errorno == 0);
if (debugger_running == false) {
- fprintf(stderr, "Starting debugger on port %d failed\n", debug_port);
+ fprintf(stderr, "Problem starting debugger: %s\n", ::uv_strerror(errorno));
fflush(stderr);
return;
}
- fprintf(stderr, "Debugger listening on port %d\n", debug_port);
- fflush(stderr);
-
- Environment* env = Environment::GetCurrentChecked(isolate);
- if (env == NULL)
- return; // Still starting up.
-
- // Assign environment to the debugger's context
- env->AssignToContext(v8::Debug::GetDebugContext());
-
+ if (wait_connect == true) Debugger::Break(env->isolate());
Context::Scope context_scope(env->context());
Local<Object> message = Object::New(env->isolate());
message->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "cmd"),
@@ -3113,17 +3101,6 @@ static void EnableDebug(Isolate* isolate, bool wait_connect) {
}
-// Called from the main thread.
-static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) {
- if (debugger_running == false) {
- fprintf(stderr, "Starting debugger agent.\n");
- EnableDebug(node_isolate, false);
- }
- Isolate::Scope isolate_scope(node_isolate);
- v8::Debug::ProcessDebugMessages();
-}
-
-
#ifdef __POSIX__
static volatile sig_atomic_t caught_early_debug_signal;
@@ -3142,9 +3119,7 @@ static void InstallEarlyDebugSignalHandler() {
static void EnableDebugSignalHandler(int signo) {
- // Call only async signal-safe functions here!
- v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
- uv_async_send(&dispatch_debug_messages_async);
+ Debugger::Break(*static_cast<Isolate* volatile*>(&node_isolate));
}
@@ -3193,8 +3168,7 @@ static int RegisterDebugSignalHandler() {
#ifdef _WIN32
DWORD WINAPI EnableDebugThreadProc(void* arg) {
- v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
- uv_async_send(&dispatch_debug_messages_async);
+ Debugger::Break(*static_cast<Isolate* volatile*>(&node_isolate));
return 0;
}
@@ -3340,7 +3314,7 @@ static void DebugProcess(const FunctionCallbackInfo<Value>& args) {
static void DebugPause(const FunctionCallbackInfo<Value>& args) {
- v8::Debug::DebugBreak(args.GetIsolate());
+ Debugger::Break(args.GetIsolate());
}
@@ -3361,13 +3335,6 @@ void Init(int* argc,
// Make inherited handles noninheritable.
uv_disable_stdio_inheritance();
- // init async debug messages dispatching
- // FIXME(bnoordhuis) Should be per-isolate or per-context, not global.
- uv_async_init(uv_default_loop(),
- &dispatch_debug_messages_async,
- DispatchDebugMessagesAsyncCallback);
- uv_unref(reinterpret_cast<uv_handle_t*>(&dispatch_debug_messages_async));
-
#if defined(NODE_V8_OPTIONS)
// Should come before the call to V8::SetFlagsFromCommandLine()
// so the user can disable a flag --foo at run-time by passing
@@ -3449,13 +3416,6 @@ void Init(int* argc,
Isolate::Scope isolate_scope(node_isolate);
V8::SetFatalErrorHandler(node::OnFatalError);
V8::AddMessageListener(OnMessage);
-
- // If the --debug flag was specified then initialize the debug thread.
- if (use_debug_agent) {
- EnableDebug(node_isolate, debug_wait_connect);
- } else {
- RegisterDebugSignalHandler();
- }
}
@@ -3610,15 +3570,12 @@ int Start(int argc, char** argv) {
Local<Context> context = Context::New(node_isolate);
Environment* env = CreateEnvironment(
node_isolate, context, argc, argv, exec_argc, exec_argv);
- // Assign env to the debugger's context
- if (debugger_running) {
- HandleScope scope(env->isolate());
- env->AssignToContext(v8::Debug::GetDebugContext());
- }
- // This Context::Scope is here so EnableDebug() can look up the current
- // environment with Environment::GetCurrentChecked().
- // TODO(bnoordhuis) Reorder the debugger initialization logic so it can
- // be removed.
+ env->debugger()->set_port(debug_port);
+ // If the --debug flag was specified then initialize the debug thread.
+ if (use_debug_agent)
+ EnableDebug(env, debug_wait_connect);
+ else
+ RegisterDebugSignalHandler();
{
Context::Scope context_scope(env->context());
bool more;
Please sign in to comment.
Something went wrong with that request. Please try again.