d62ea57f8e
This is the core functionality needed for the debugger. Review URL: https://codereview.chromium.org//23067006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26328 260f80e4-7a28-3924-810f-c04153c831b5
467 lines
14 KiB
C++
467 lines
14 KiB
C++
// 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/log.h"
|
|
#include "bin/socket.h"
|
|
#include "bin/thread.h"
|
|
#include "bin/utils.h"
|
|
|
|
#include "platform/globals.h"
|
|
#include "platform/json.h"
|
|
#include "platform/thread.h"
|
|
#include "platform/utils.h"
|
|
|
|
#include "include/dart_api.h"
|
|
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
int DebuggerConnectionHandler::listener_fd_ = -1;
|
|
dart::Monitor* DebuggerConnectionHandler::handler_lock_ = new dart::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;
|
|
|
|
|
|
class MessageBuffer {
|
|
public:
|
|
explicit MessageBuffer(int 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_;
|
|
int fd_;
|
|
int data_length_;
|
|
bool connection_is_alive_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(MessageBuffer);
|
|
};
|
|
|
|
|
|
MessageBuffer::MessageBuffer(int 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(int 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;
|
|
}
|
|
|
|
// 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 =
|
|
static_cast<Dart_IsolateId>(msg_parser.GetIntParam("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(int 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.
|
|
}
|
|
|
|
|
|
int DebuggerConnectionHandler::StartHandler(const char* address,
|
|
int port_number) {
|
|
ASSERT(handler_lock_ != NULL);
|
|
MonitorLocker ml(handler_lock_);
|
|
if (listener_fd_ != -1) {
|
|
// 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;
|
|
port_number = Socket::GetPort(listener_fd_);
|
|
DebuggerConnectionImpl::StartHandler(port_number);
|
|
return port_number;
|
|
}
|
|
|
|
|
|
void DebuggerConnectionHandler::WaitForConnection() {
|
|
ASSERT(handler_lock_ != NULL);
|
|
MonitorLocker ml(handler_lock_);
|
|
while (!IsConnected()) {
|
|
dart::Monitor::WaitResult res = ml.Wait();
|
|
ASSERT(res == dart::Monitor::kNotified);
|
|
}
|
|
}
|
|
|
|
|
|
void DebuggerConnectionHandler::SendMsg(int 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_);
|
|
// 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(int debug_fd,
|
|
dart::TextBuffer* msg) {
|
|
ASSERT(debug_fd >= 0);
|
|
ASSERT(IsValidJSON(msg->buf()));
|
|
// 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.
|
|
dart::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(int 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 =
|
|
static_cast<Dart_IsolateId>(msg_parser.GetIntParam("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(int 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(int 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(int 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
|