| // Copyright Fedor Indutny and other Node contributors. |
| // |
| // 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 "debug-agent.h" |
| |
| #include "node.h" |
| #include "node_internals.h" // ARRAY_SIZE |
| #include "env.h" |
| #include "env-inl.h" |
| #include "v8.h" |
| #include "v8-debug.h" |
| #include "util.h" |
| #include "util-inl.h" |
| #include "queue.h" |
| |
| #include <string.h> |
| |
| namespace node { |
| namespace debugger { |
| |
| using v8::Context; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::Handle; |
| using v8::HandleScope; |
| using v8::Integer; |
| using v8::Isolate; |
| using v8::Local; |
| using v8::Locker; |
| using v8::Object; |
| using v8::String; |
| using v8::Value; |
| |
| |
| Agent::Agent(Environment* env) : state_(kNone), |
| port_(5858), |
| wait_(false), |
| parent_env_(env), |
| child_env_(nullptr), |
| dispatch_handler_(nullptr) { |
| int err; |
| |
| err = uv_sem_init(&start_sem_, 0); |
| CHECK_EQ(err, 0); |
| |
| err = uv_mutex_init(&message_mutex_); |
| CHECK_EQ(err, 0); |
| |
| QUEUE_INIT(&messages_); |
| } |
| |
| |
| Agent::~Agent() { |
| Stop(); |
| |
| uv_sem_destroy(&start_sem_); |
| uv_mutex_destroy(&message_mutex_); |
| |
| // Clean-up messages |
| while (!QUEUE_EMPTY(&messages_)) { |
| QUEUE* q = QUEUE_HEAD(&messages_); |
| QUEUE_REMOVE(q); |
| AgentMessage* msg = ContainerOf(&AgentMessage::member, q); |
| delete msg; |
| } |
| } |
| |
| |
| bool Agent::Start(int port, bool wait) { |
| int err; |
| |
| if (state_ == kRunning) |
| return false; |
| |
| err = uv_loop_init(&child_loop_); |
| if (err != 0) |
| goto loop_init_failed; |
| |
| // Interruption signal handler |
| err = uv_async_init(&child_loop_, &child_signal_, ChildSignalCb); |
| if (err != 0) |
| goto async_init_failed; |
| uv_unref(reinterpret_cast<uv_handle_t*>(&child_signal_)); |
| |
| port_ = port; |
| wait_ = wait; |
| |
| err = uv_thread_create(&thread_, |
| reinterpret_cast<uv_thread_cb>(ThreadCb), |
| this); |
| if (err != 0) |
| goto thread_create_failed; |
| |
| uv_sem_wait(&start_sem_); |
| |
| state_ = kRunning; |
| |
| return true; |
| |
| thread_create_failed: |
| uv_close(reinterpret_cast<uv_handle_t*>(&child_signal_), nullptr); |
| |
| async_init_failed: |
| err = uv_loop_close(&child_loop_); |
| CHECK_EQ(err, 0); |
| |
| loop_init_failed: |
| return false; |
| } |
| |
| |
| void Agent::Enable() { |
| v8::Debug::SetMessageHandler(MessageHandler); |
| |
| // Assign environment to the debugger's context |
| // NOTE: The debugger context is created after `SetMessageHandler()` call |
| parent_env()->AssignToContext(v8::Debug::GetDebugContext()); |
| } |
| |
| |
| void Agent::Stop() { |
| int err; |
| |
| if (state_ != kRunning) { |
| return; |
| } |
| |
| v8::Debug::SetMessageHandler(nullptr); |
| |
| // Send empty message to terminate things |
| EnqueueMessage(new AgentMessage(nullptr, 0)); |
| |
| // Signal worker thread to make it stop |
| err = uv_async_send(&child_signal_); |
| CHECK_EQ(err, 0); |
| |
| err = uv_thread_join(&thread_); |
| CHECK_EQ(err, 0); |
| |
| uv_close(reinterpret_cast<uv_handle_t*>(&child_signal_), nullptr); |
| uv_run(&child_loop_, UV_RUN_NOWAIT); |
| |
| err = uv_loop_close(&child_loop_); |
| CHECK_EQ(err, 0); |
| |
| state_ = kNone; |
| } |
| |
| |
| void Agent::WorkerRun() { |
| static const char* argv[] = { "node", "--debug-agent" }; |
| Isolate* isolate = Isolate::New(); |
| { |
| Locker locker(isolate); |
| Isolate::Scope isolate_scope(isolate); |
| |
| HandleScope handle_scope(isolate); |
| Local<Context> context = Context::New(isolate); |
| |
| Context::Scope context_scope(context); |
| Environment* env = CreateEnvironment( |
| isolate, |
| &child_loop_, |
| context, |
| ARRAY_SIZE(argv), |
| argv, |
| ARRAY_SIZE(argv), |
| argv); |
| |
| child_env_ = env; |
| |
| // Expose API |
| InitAdaptor(env); |
| LoadEnvironment(env); |
| |
| CHECK_EQ(&child_loop_, env->event_loop()); |
| uv_run(&child_loop_, UV_RUN_DEFAULT); |
| |
| // Clean-up peristent |
| api_.Reset(); |
| |
| // Clean-up all running handles |
| env->CleanupHandles(); |
| |
| env->Dispose(); |
| env = nullptr; |
| } |
| isolate->Dispose(); |
| } |
| |
| |
| void Agent::InitAdaptor(Environment* env) { |
| Isolate* isolate = env->isolate(); |
| HandleScope scope(isolate); |
| |
| // Create API adaptor |
| Local<FunctionTemplate> t = FunctionTemplate::New(isolate); |
| t->InstanceTemplate()->SetInternalFieldCount(1); |
| t->SetClassName(String::NewFromUtf8(isolate, "DebugAPI")); |
| |
| NODE_SET_PROTOTYPE_METHOD(t, "notifyListen", NotifyListen); |
| NODE_SET_PROTOTYPE_METHOD(t, "notifyWait", NotifyWait); |
| NODE_SET_PROTOTYPE_METHOD(t, "sendCommand", SendCommand); |
| |
| Local<Object> api = t->GetFunction()->NewInstance(); |
| api->SetAlignedPointerInInternalField(0, this); |
| |
| api->Set(String::NewFromUtf8(isolate, "port"), Integer::New(isolate, port_)); |
| |
| env->process_object()->Set(String::NewFromUtf8(isolate, "_debugAPI"), api); |
| api_.Reset(env->isolate(), api); |
| } |
| |
| |
| Agent* Agent::Unwrap(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| void* ptr = args.Holder()->GetAlignedPointerFromInternalField(0); |
| return reinterpret_cast<Agent*>(ptr); |
| } |
| |
| |
| void Agent::NotifyListen(const FunctionCallbackInfo<Value>& args) { |
| Agent* a = Unwrap(args); |
| |
| // Notify other thread that we are ready to process events |
| uv_sem_post(&a->start_sem_); |
| } |
| |
| |
| void Agent::NotifyWait(const FunctionCallbackInfo<Value>& args) { |
| Agent* a = Unwrap(args); |
| |
| a->wait_ = false; |
| |
| int err = uv_async_send(&a->child_signal_); |
| CHECK_EQ(err, 0); |
| } |
| |
| |
| void Agent::SendCommand(const FunctionCallbackInfo<Value>& args) { |
| Agent* a = Unwrap(args); |
| Environment* env = a->child_env(); |
| HandleScope scope(env->isolate()); |
| |
| String::Value v(args[0]); |
| |
| v8::Debug::SendCommand(a->parent_env()->isolate(), *v, v.length()); |
| if (a->dispatch_handler_ != nullptr) |
| a->dispatch_handler_(a->parent_env()); |
| } |
| |
| |
| void Agent::ThreadCb(Agent* agent) { |
| agent->WorkerRun(); |
| } |
| |
| |
| void Agent::ChildSignalCb(uv_async_t* signal) { |
| Agent* a = ContainerOf(&Agent::child_signal_, signal); |
| Isolate* isolate = a->child_env()->isolate(); |
| |
| HandleScope scope(isolate); |
| Local<Object> api = PersistentToLocal(isolate, a->api_); |
| |
| uv_mutex_lock(&a->message_mutex_); |
| while (!QUEUE_EMPTY(&a->messages_)) { |
| QUEUE* q = QUEUE_HEAD(&a->messages_); |
| AgentMessage* msg = ContainerOf(&AgentMessage::member, q); |
| |
| // Time to close everything |
| if (msg->data() == nullptr) { |
| QUEUE_REMOVE(q); |
| delete msg; |
| |
| MakeCallback(isolate, api, "onclose", 0, nullptr); |
| break; |
| } |
| |
| // Waiting for client, do not send anything just yet |
| // TODO(indutny): move this to js-land |
| if (a->wait_) |
| break; |
| |
| QUEUE_REMOVE(q); |
| Local<Value> argv[] = { |
| String::NewFromTwoByte(isolate, |
| msg->data(), |
| String::kNormalString, |
| msg->length()) |
| }; |
| |
| // Emit message |
| MakeCallback(isolate, |
| api, |
| "onmessage", |
| ARRAY_SIZE(argv), |
| argv); |
| delete msg; |
| } |
| uv_mutex_unlock(&a->message_mutex_); |
| } |
| |
| |
| void Agent::EnqueueMessage(AgentMessage* message) { |
| uv_mutex_lock(&message_mutex_); |
| QUEUE_INSERT_TAIL(&messages_, &message->member); |
| uv_mutex_unlock(&message_mutex_); |
| uv_async_send(&child_signal_); |
| } |
| |
| |
| void Agent::MessageHandler(const v8::Debug::Message& message) { |
| Isolate* isolate = message.GetIsolate(); |
| Environment* env = Environment::GetCurrent(isolate); |
| Agent* a = env->debugger_agent(); |
| CHECK_NE(a, nullptr); |
| CHECK_EQ(isolate, a->parent_env()->isolate()); |
| |
| HandleScope scope(isolate); |
| Local<String> json = message.GetJSON(); |
| String::Value v(json); |
| |
| AgentMessage* msg = new AgentMessage(*v, v.length()); |
| a->EnqueueMessage(msg); |
| } |
| |
| } // namespace debugger |
| } // namespace node |