// Copyright (c) 2014 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/node_debugger.h" #include #include "base/bind.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" #include "net/test/embedded_test_server/tcp_listen_socket.h" #include "atom/common/node_includes.h" namespace atom { namespace { // NodeDebugger is stored in Isolate's data, slots 0, 1, 3 have already been // taken by gin, blink and node, using 2 is a safe option for now. const int kIsolateSlot = 2; const char* kContentLength = "Content-Length"; } // namespace NodeDebugger::NodeDebugger(v8::Isolate* isolate) : isolate_(isolate), thread_("NodeDebugger"), content_length_(-1), weak_factory_(this) { bool use_debug_agent = false; int port = 5858; std::string port_str; base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); if (cmd->HasSwitch("debug")) { use_debug_agent = true; port_str = cmd->GetSwitchValueASCII("debug"); } else if (cmd->HasSwitch("debug-brk")) { use_debug_agent = true; port_str = cmd->GetSwitchValueASCII("debug-brk"); } if (use_debug_agent) { if (!port_str.empty()) base::StringToInt(port_str, &port); isolate_->SetData(kIsolateSlot, this); v8::Debug::SetMessageHandler(isolate_, DebugMessageHandler); weak_up_ui_handle_.data = this; uv_async_init(uv_default_loop(), &weak_up_ui_handle_, ProcessMessageInUI); // Start a new IO thread. base::Thread::Options options; options.message_loop_type = base::MessageLoop::Type::IO; if (!thread_.StartWithOptions(options)) { LOG(ERROR) << "Unable to start debugger thread"; return; } // Start the server in new IO thread. thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&NodeDebugger::StartServer, weak_factory_.GetWeakPtr(), port)); } } NodeDebugger::~NodeDebugger() { thread_.Stop(); } bool NodeDebugger::IsRunning() const { return thread_.IsRunning(); } void NodeDebugger::StartServer(int port) { server_ = net::test_server::TCPListenSocket::CreateAndListen( "127.0.0.1", port, this); if (!server_) { LOG(ERROR) << "Cannot start debugger server"; return; } } void NodeDebugger::CloseSession() { accepted_socket_.reset(); } void NodeDebugger::OnMessage(const std::string& message) { if (message.find("\"type\":\"request\",\"command\":\"disconnect\"}") != std::string::npos) CloseSession(); base::string16 message16 = base::UTF8ToUTF16(message); v8::Debug::SendCommand( isolate_, reinterpret_cast(message16.data()), message16.size()); uv_async_send(&weak_up_ui_handle_); } void NodeDebugger::SendMessage(const std::string& message) { if (accepted_socket_) { std::string header = base::StringPrintf( "%s: %d\r\n\r\n", kContentLength, static_cast(message.size())); accepted_socket_->Send(header); accepted_socket_->Send(message); } } void NodeDebugger::SendConnectMessage() { accepted_socket_->Send(base::StringPrintf( "Type: connect\r\n" "V8-Version: %s\r\n" "Protocol-Version: 1\r\n" "Embedding-Host: %s\r\n" "%s: 0\r\n", v8::V8::GetVersion(), ATOM_PRODUCT_NAME, kContentLength), true); } // static void NodeDebugger::ProcessMessageInUI(uv_async_t* handle) { NodeDebugger* self = static_cast(handle->data); v8::Debug::ProcessDebugMessages(self->isolate_); } // static void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { NodeDebugger* self = static_cast( message.GetIsolate()->GetData(kIsolateSlot)); if (self) { std::string message8(*v8::String::Utf8Value(message.GetJSON())); self->thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&NodeDebugger::SendMessage, self->weak_factory_.GetWeakPtr(), message8)); } } void NodeDebugger::DidAccept( net::test_server::StreamListenSocket* server, std::unique_ptr socket) { // Only accept one session. if (accepted_socket_) { socket->Send(std::string("Remote debugging session already active"), true); return; } accepted_socket_ = std::move(socket); SendConnectMessage(); } void NodeDebugger::DidRead(net::test_server::StreamListenSocket* socket, const char* data, int len) { buffer_.append(data, len); do { if (buffer_.empty()) return; // Read the "Content-Length" header. if (content_length_ < 0) { size_t pos = buffer_.find("\r\n\r\n"); if (pos == std::string::npos) return; // We can be sure that the header is "Content-Length: xxx\r\n". std::string content_length = buffer_.substr(16, pos - 16); if (!base::StringToInt(content_length, &content_length_)) { DidClose(accepted_socket_.get()); return; } // Strip header from buffer. buffer_ = buffer_.substr(pos + 4); } // Read the message. if (buffer_.size() >= static_cast(content_length_)) { std::string message = buffer_.substr(0, content_length_); buffer_ = buffer_.substr(content_length_); OnMessage(message); // Get ready for next message. content_length_ = -1; } } while (true); } void NodeDebugger::DidClose(net::test_server::StreamListenSocket* socket) { // If we lost the connection, then simulate a disconnect msg: OnMessage("{\"seq\":1,\"type\":\"request\",\"command\":\"disconnect\"}"); } } // namespace atom