| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "bin/dbg_connection.h" |
| #include "bin/dbg_message.h" |
| #include "bin/dartutils.h" |
| #include "bin/lockers.h" |
| #include "bin/log.h" |
| #include "bin/socket.h" |
| #include "bin/thread.h" |
| #include "bin/utils.h" |
| |
| #include "platform/globals.h" |
| #include "platform/json.h" |
| #include "platform/utils.h" |
| |
| #include "include/dart_api.h" |
| |
| |
| namespace dart { |
| namespace bin { |
| |
| bool trace_debug_protocol = false; |
| |
| intptr_t DebuggerConnectionHandler::listener_fd_ = -1; |
| Monitor* DebuggerConnectionHandler::handler_lock_ = new Monitor(); |
| |
| // TODO(asiva): Remove this once we have support for multiple debugger |
| // connections. For now we just store the single debugger connection |
| // handler in a static variable. |
| static DebuggerConnectionHandler* singleton_handler = NULL; |
| |
| // The maximum message length to print when --trace_debug_protocol is |
| // specified. |
| static const int kMaxPrintMessageLen = 1024; |
| |
| class MessageBuffer { |
| public: |
| explicit MessageBuffer(intptr_t fd); |
| ~MessageBuffer(); |
| void ReadData(); |
| bool IsValidMessage() const; |
| void PopMessage(); |
| int MessageId() const; |
| char* buf() const { return buf_; } |
| bool Alive() const { return connection_is_alive_; } |
| |
| private: |
| static const int kInitialBufferSize = 256; |
| char* buf_; |
| int buf_length_; |
| intptr_t fd_; |
| int data_length_; |
| bool connection_is_alive_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MessageBuffer); |
| }; |
| |
| |
| MessageBuffer::MessageBuffer(intptr_t fd) |
| : buf_(NULL), |
| buf_length_(0), |
| fd_(fd), |
| data_length_(0), |
| connection_is_alive_(true) { |
| buf_ = reinterpret_cast<char*>(malloc(kInitialBufferSize)); |
| if (buf_ == NULL) { |
| FATAL("Failed to allocate message buffer\n"); |
| } |
| buf_length_ = kInitialBufferSize; |
| buf_[0] = '\0'; |
| data_length_ = 0; |
| } |
| |
| |
| MessageBuffer::~MessageBuffer() { |
| free(buf_); |
| buf_ = NULL; |
| fd_ = -1; |
| } |
| |
| |
| bool MessageBuffer::IsValidMessage() const { |
| if (data_length_ == 0) { |
| return false; |
| } |
| dart::JSONReader msg_reader(buf_); |
| return msg_reader.EndOfObject() != NULL; |
| } |
| |
| |
| int MessageBuffer::MessageId() const { |
| dart::JSONReader r(buf_); |
| r.Seek("id"); |
| if (r.Type() == dart::JSONReader::kInteger) { |
| return atoi(r.ValueChars()); |
| } else { |
| return -1; |
| } |
| } |
| |
| |
| void MessageBuffer::ReadData() { |
| ASSERT(data_length_ >= 0); |
| ASSERT(data_length_ < buf_length_); |
| int max_read = buf_length_ - data_length_ - 1; |
| if (max_read == 0) { |
| // TODO(hausner): |
| // Buffer is full. What should we do if there is no valid message |
| // in the buffer? This might be possible if the client sends a message |
| // that's larger than the buffer, of if the client sends malformed |
| // messages that keep piling up. |
| ASSERT(IsValidMessage()); |
| return; |
| } |
| // TODO(hausner): Handle error conditions returned by Read. We may |
| // want to close the debugger connection if we get any errors. |
| int bytes_read = |
| DebuggerConnectionImpl::Receive(fd_, buf_ + data_length_, max_read); |
| if (bytes_read == 0) { |
| connection_is_alive_ = false; |
| return; |
| } |
| ASSERT(bytes_read > 0); |
| data_length_ += bytes_read; |
| ASSERT(data_length_ < buf_length_); |
| buf_[data_length_] = '\0'; |
| } |
| |
| |
| void MessageBuffer::PopMessage() { |
| dart::JSONReader msg_reader(buf_); |
| const char* end = msg_reader.EndOfObject(); |
| if (end != NULL) { |
| ASSERT(*end == '}'); |
| end++; |
| data_length_ = 0; |
| while (*end != '\0') { |
| buf_[data_length_] = *end++; |
| data_length_++; |
| } |
| buf_[data_length_] = '\0'; |
| ASSERT(data_length_ < buf_length_); |
| } |
| } |
| |
| |
| static bool IsValidJSON(const char* msg) { |
| dart::JSONReader r(msg); |
| return r.CheckMessage(); |
| } |
| |
| |
| DebuggerConnectionHandler::DebuggerConnectionHandler(intptr_t debug_fd) |
| : debug_fd_(debug_fd), msgbuf_(NULL) { |
| msgbuf_ = new MessageBuffer(debug_fd_); |
| } |
| |
| |
| DebuggerConnectionHandler::~DebuggerConnectionHandler() { |
| CloseDbgConnection(); |
| DebuggerConnectionHandler::RemoveDebuggerConnection(debug_fd_); |
| } |
| |
| |
| int DebuggerConnectionHandler::MessageId() { |
| ASSERT(msgbuf_ != NULL); |
| return msgbuf_->MessageId(); |
| } |
| |
| |
| void DebuggerConnectionHandler::HandleUnknownMsg() { |
| int msg_id = msgbuf_->MessageId(); |
| ASSERT(msg_id >= 0); |
| SendError(debug_fd_, msg_id, "unknown debugger command"); |
| } |
| |
| |
| typedef void (*CommandHandler)(DbgMessage* msg); |
| |
| struct JSONDebuggerCommand { |
| const char* cmd_string; |
| CommandHandler handler_function; |
| }; |
| |
| |
| void DebuggerConnectionHandler::HandleMessages() { |
| static JSONDebuggerCommand generic_debugger_commands[] = { |
| { "interrupt", HandleInterruptCmd }, |
| { "getIsolateIds", HandleIsolatesListCmd }, |
| { NULL, NULL } |
| }; |
| |
| for (;;) { |
| // Read a message. |
| while (!msgbuf_->IsValidMessage() && msgbuf_->Alive()) { |
| msgbuf_->ReadData(); |
| } |
| if (!msgbuf_->Alive()) { |
| if (trace_debug_protocol) { |
| Log::Print("Debugger is exiting HandleMessages loop.\n"); |
| } |
| return; |
| } |
| |
| if (trace_debug_protocol) { |
| dart::JSONReader r(msgbuf_->buf()); |
| const char* msg_end = r.EndOfObject(); |
| if (msg_end != NULL) { |
| intptr_t msg_len = msg_end - msgbuf_->buf(); |
| int print_len = ((msg_len > kMaxPrintMessageLen) |
| ? kMaxPrintMessageLen : msg_len); |
| Log::Print("[<<<] Receiving message from debug fd %" Pd ":\n%.*s%s\n", |
| debug_fd_, print_len, msgbuf_->buf(), |
| ((msg_len > print_len) ? "..." : "")); |
| } |
| } |
| |
| // Parse out the command portion from the message. |
| dart::JSONReader r(msgbuf_->buf()); |
| bool found = r.Seek("command"); |
| if (r.Error()) { |
| FATAL("Illegal JSON message received"); |
| } |
| if (!found) { |
| Log::Print("'command' not found in JSON message: '%s'\n", |
| msgbuf_->buf()); |
| msgbuf_->PopMessage(); |
| } |
| |
| // Check if this is a generic command (not isolate specific). |
| int i = 0; |
| bool is_handled = false; |
| while (generic_debugger_commands[i].cmd_string != NULL) { |
| if (r.IsStringLiteral(generic_debugger_commands[i].cmd_string)) { |
| DbgMessage* msg = new DbgMessage(i, |
| msgbuf_->buf(), |
| r.EndOfObject(), |
| debug_fd_); |
| (*generic_debugger_commands[i].handler_function)(msg); |
| is_handled = true; |
| msgbuf_->PopMessage(); |
| delete msg; |
| break; |
| } |
| i++; |
| } |
| if (!is_handled) { |
| // Check if this is an isolate specific command. |
| int32_t cmd_idx = DbgMsgQueueList::LookupIsolateCommand(r.ValueChars(), |
| r.ValueLen()); |
| if (cmd_idx != DbgMsgQueueList::kInvalidCommand) { |
| const char* start = msgbuf_->buf(); |
| const char* end = r.EndOfObject(); |
| // Get debug message queue corresponding to isolate. |
| MessageParser msg_parser(start, (end - start)); |
| Dart_IsolateId isolate_id = msg_parser.GetInt64Param("isolateId"); |
| if (!DbgMsgQueueList::AddIsolateMessage(isolate_id, |
| cmd_idx, |
| msgbuf_->buf(), |
| r.EndOfObject(), |
| debug_fd_)) { |
| SendError(debug_fd_, MessageId(), "Invalid isolate specified"); |
| } |
| msgbuf_->PopMessage(); |
| continue; |
| } |
| |
| // This is an unrecognized command, report error and move on to next. |
| Log::Print("unrecognized command received: '%s'\n", msgbuf_->buf()); |
| HandleUnknownMsg(); |
| msgbuf_->PopMessage(); |
| } |
| } |
| } |
| |
| |
| void DebuggerConnectionHandler::SendError(intptr_t debug_fd, |
| int msg_id, |
| const char* err_msg) { |
| dart::TextBuffer msg(64); |
| msg.Printf("{\"id\": %d, \"error\": \"Error: ", msg_id); |
| msg.AddEscapedString(err_msg); |
| msg.Printf("\"}"); |
| SendMsg(debug_fd, &msg); |
| } |
| |
| |
| void DebuggerConnectionHandler::CloseDbgConnection() { |
| if (debug_fd_ >= 0) { |
| Socket::Close(debug_fd_); |
| } |
| if (msgbuf_ != NULL) { |
| delete msgbuf_; |
| msgbuf_ = NULL; |
| } |
| // TODO(hausner): Need to tell the VM debugger object to remove all |
| // breakpoints. |
| } |
| |
| |
| // The vm service relies on certain debugger functionality. |
| void DebuggerConnectionHandler::InitForVmService() { |
| MonitorLocker ml(handler_lock_); |
| DbgMsgQueueList::Initialize(); |
| } |
| |
| |
| int DebuggerConnectionHandler::StartHandler(const char* address, |
| int port_number) { |
| ASSERT(handler_lock_ != NULL); |
| MonitorLocker ml(handler_lock_); |
| if (IsListening()) { |
| // The debugger connection handler was already started. |
| return Socket::GetPort(listener_fd_); |
| } |
| |
| // First setup breakpoint, exception and delayed breakpoint handlers. |
| DbgMsgQueueList::Initialize(); |
| |
| // Initialize the socket implementation. |
| if (!Socket::Initialize()) { |
| FATAL("Failed initializing socket implementation."); |
| } |
| |
| // Now setup a listener socket and start a thread which will |
| // listen, accept connections from debuggers, read and handle/dispatch |
| // debugger commands received on these connections. |
| ASSERT(listener_fd_ == -1); |
| OSError *os_error; |
| AddressList<SocketAddress>* addresses = |
| Socket::LookupAddress(address, -1, &os_error); |
| RawAddr addr = addresses->GetAt(0)->addr(); |
| SocketAddress::SetAddrPort(&addr, port_number); |
| listener_fd_ = ServerSocket::CreateBindListen(addr, 1); |
| delete addresses; |
| if (listener_fd_ < 0) { |
| fprintf(stderr, "%s", "Could not initialize debug socket\n"); |
| fflush(stderr); |
| exit(255); |
| } |
| |
| port_number = Socket::GetPort(listener_fd_); |
| DebuggerConnectionImpl::StartHandler(port_number); |
| return port_number; |
| } |
| |
| |
| void DebuggerConnectionHandler::StopHandler() { |
| if (IsConnected()) { |
| DebuggerConnectionImpl::StopHandler(singleton_handler->debug_fd()); |
| } |
| } |
| |
| |
| void DebuggerConnectionHandler::WaitForConnection() { |
| ASSERT(handler_lock_ != NULL); |
| MonitorLocker ml(handler_lock_); |
| if (!IsListening()) { |
| // If we are only running the vm service, don't wait for |
| // connections. |
| return; |
| } |
| while (!IsConnected()) { |
| Monitor::WaitResult res = ml.Wait(); |
| ASSERT(res == Monitor::kNotified); |
| } |
| } |
| |
| |
| void DebuggerConnectionHandler::SendMsg(intptr_t debug_fd, |
| dart::TextBuffer* msg) { |
| ASSERT(handler_lock_ != NULL); |
| MonitorLocker ml(handler_lock_); |
| SendMsgHelper(debug_fd, msg); |
| } |
| |
| |
| void DebuggerConnectionHandler::BroadcastMsg(dart::TextBuffer* msg) { |
| ASSERT(handler_lock_ != NULL); |
| MonitorLocker ml(handler_lock_); |
| if (!IsListening()) { |
| // If we are only running the vm service, don't try to broadcast |
| // to debugger clients. |
| return; |
| } |
| // TODO(asiva): Once we support connection to multiple debuggers |
| // we need to send the message to all of them. |
| ASSERT(singleton_handler != NULL); |
| SendMsgHelper(singleton_handler->debug_fd(), msg); |
| } |
| |
| |
| void DebuggerConnectionHandler::SendMsgHelper(intptr_t debug_fd, |
| dart::TextBuffer* msg) { |
| ASSERT(debug_fd >= 0); |
| ASSERT(IsValidJSON(msg->buf())); |
| |
| if (trace_debug_protocol) { |
| int print_len = ((msg->length() > kMaxPrintMessageLen) |
| ? kMaxPrintMessageLen : msg->length()); |
| Log::Print("[>>>] Sending message to debug fd %" Pd ":\n%.*s%s\n", |
| debug_fd, print_len, msg->buf(), |
| ((msg->length() > print_len) ? "..." : "")); |
| } |
| |
| // Sending messages in short pieces can be used to stress test the |
| // debugger front-end's message handling code. |
| const bool send_in_pieces = false; |
| if (send_in_pieces) { |
| intptr_t remaining = msg->length(); |
| intptr_t sent = 0; |
| const intptr_t max_piece_len = 122; // Pretty arbitrary, not a power of 2. |
| Monitor sleep; |
| while (remaining > 0) { |
| intptr_t piece_len = remaining; |
| if (piece_len > max_piece_len) { |
| piece_len = max_piece_len; |
| } |
| intptr_t written = |
| DebuggerConnectionImpl::Send(debug_fd, msg->buf() + sent, piece_len); |
| ASSERT(written == piece_len); |
| sent += written; |
| remaining -= written; |
| // Wait briefly so the OS does not coalesce message fragments. |
| { |
| MonitorLocker ml(&sleep); |
| ml.Wait(10); |
| } |
| } |
| return; |
| } |
| intptr_t bytes_written = |
| DebuggerConnectionImpl::Send(debug_fd, msg->buf(), msg->length()); |
| ASSERT(msg->length() == bytes_written); |
| // TODO(hausner): Error checking. Probably just shut down the debugger |
| // session if we there is an error while writing. |
| } |
| |
| |
| void DebuggerConnectionHandler::AcceptDbgConnection(intptr_t debug_fd) { |
| Socket::SetNoDelay(debug_fd, true); |
| AddNewDebuggerConnection(debug_fd); |
| { |
| ASSERT(handler_lock_ != NULL); |
| MonitorLocker ml(handler_lock_); |
| ml.NotifyAll(); |
| } |
| // TODO(asiva): Once we implement support for multiple connections |
| // we should have a different callback for wakeups on fds which |
| // are not the listener_fd_. |
| // In that callback we would lookup the handler object |
| // corresponding to that fd and invoke HandleMessages on it. |
| // For now we run that code here. |
| DebuggerConnectionHandler* handler = GetDebuggerConnectionHandler(debug_fd); |
| if (handler != NULL) { |
| handler->HandleMessages(); |
| delete handler; |
| } |
| } |
| |
| |
| void DebuggerConnectionHandler::HandleInterruptCmd(DbgMessage* in_msg) { |
| MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len()); |
| int msg_id = msg_parser.MessageId(); |
| Dart_IsolateId isolate_id = msg_parser.GetInt64Param("isolateId"); |
| if (isolate_id == ILLEGAL_ISOLATE_ID || Dart_GetIsolate(isolate_id) == NULL) { |
| in_msg->SendErrorReply(msg_id, "Invalid isolate specified"); |
| return; |
| } |
| if (!DbgMsgQueueList::InterruptIsolate(isolate_id)) { |
| in_msg->SendErrorReply(msg_id, "Invalid isolate specified"); |
| return; |
| } |
| dart::TextBuffer msg(64); |
| msg.Printf("{ \"id\": %d }", msg_id); |
| in_msg->SendReply(&msg); |
| } |
| |
| |
| void DebuggerConnectionHandler::HandleIsolatesListCmd(DbgMessage* in_msg) { |
| MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len()); |
| int msg_id = msg_parser.MessageId(); |
| ASSERT(msg_id >= 0); |
| dart::TextBuffer msg(64); |
| msg.Printf("{ \"id\": %d, \"result\": { \"isolateIds\": [", msg_id); |
| DbgMsgQueueList::ListIsolateIds(&msg); |
| msg.Printf("]}}"); |
| in_msg->SendReply(&msg); |
| } |
| |
| |
| void DebuggerConnectionHandler::AddNewDebuggerConnection(intptr_t debug_fd) { |
| // TODO(asiva): Support multiple debugger connections, for now we just |
| // create one handler, store it in a static variable and use it. |
| ASSERT(singleton_handler == NULL); |
| singleton_handler = new DebuggerConnectionHandler(debug_fd); |
| } |
| |
| |
| void DebuggerConnectionHandler::RemoveDebuggerConnection(intptr_t debug_fd) { |
| // TODO(asiva): Support multiple debugger connections, for now we just |
| // set the static handler back to NULL. |
| ASSERT(singleton_handler != NULL); |
| singleton_handler = NULL; |
| } |
| |
| |
| DebuggerConnectionHandler* |
| DebuggerConnectionHandler::GetDebuggerConnectionHandler(intptr_t debug_fd) { |
| // TODO(asiva): Support multiple debugger connections, for now we just |
| // return the one static handler that was created. |
| ASSERT(singleton_handler != NULL); |
| return singleton_handler; |
| } |
| |
| |
| bool DebuggerConnectionHandler::IsConnected() { |
| // TODO(asiva): Support multiple debugger connections. |
| // Return true if a connection has been established. |
| return singleton_handler != NULL; |
| } |
| |
| } // namespace bin |
| } // namespace dart |