blob: a63cf44afc5b8a005d301a2f69ab16dabe601777 [file] [log] [blame]
// 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 {
extern bool trace_debug_protocol;
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()) {
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);
listener_fd_ = ServerSocket::CreateBindListen(
addresses->GetAt(0)->addr(), port_number, 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::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