From 6ef7ef657e9fa60d334445f77e9ad70c22356e31 Mon Sep 17 00:00:00 2001 From: "sgjesse@chromium.org" Date: Wed, 4 Mar 2009 09:42:51 +0000 Subject: [PATCH] Add remote debugging front end to developer shell. D8 now supports demote debuggign of a V8 instance with the debugger agent enabled. Running D8 with the option --remote-debugger will try to connect to a V8 debugger agent and process the debugging protocol. The command line UI is the same as for the D8 in-process debugger as the same code is used for processing the debugger JSON. Review URL: http://codereview.chromium.org/40011 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1411 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/d8-debug.cc | 195 +++++++++++++++++++++++++++++++++++++++++ src/d8-debug.h | 107 ++++++++++++++++++++++ src/d8.cc | 10 ++- src/flag-definitions.h | 2 + 4 files changed, 312 insertions(+), 2 deletions(-) diff --git a/src/d8-debug.cc b/src/d8-debug.cc index 4240ddfec9..5b2181684e 100644 --- a/src/d8-debug.cc +++ b/src/d8-debug.cc @@ -28,6 +28,8 @@ #include "d8.h" #include "d8-debug.h" +#include "platform.h" +#include "debug-agent.h" namespace v8 { @@ -142,4 +144,197 @@ void HandleDebugEvent(DebugEvent event, } +void RunRemoteDebugger(int port) { + RemoteDebugger debugger(port); + debugger.Run(); +} + + +void RemoteDebugger::Run() { + bool ok; + + // Make sure that socket support is initialized. + ok = i::Socket::Setup(); + if (!ok) { + printf("Unable to initialize socket support %d\n", i::Socket::LastError()); + return; + } + + // Connect to the debugger agent. + conn_ = i::OS::CreateSocket(); + static const int kPortStrSize = 6; + char port_str[kPortStrSize]; + i::OS::SNPrintF(i::Vector(port_str, kPortStrSize), "%d", port_); + ok = conn_->Connect("localhost", port_str); + if (!ok) { + printf("Unable to connect to debug agent %d\n", i::Socket::LastError()); + return; + } + + // Start the receiver thread. + ReceiverThread receiver(this); + receiver.Start(); + + // Start the keyboard thread. + KeyboardThread keyboard(this); + keyboard.Start(); + + // Process events received from debugged VM and from the keyboard. + bool terminate = false; + while (!terminate) { + event_available_->Wait(); + RemoteDebuggerEvent* event = GetEvent(); + switch (event->type()) { + case RemoteDebuggerEvent::kMessage: + HandleMessageReceived(event->data()); + break; + case RemoteDebuggerEvent::kKeyboard: + HandleKeyboardCommand(event->data()); + break; + case RemoteDebuggerEvent::kDisconnect: + terminate = true; + break; + + default: + UNREACHABLE(); + } + delete event; + } + + // Wait for the receiver thread to end. + receiver.Join(); +} + + +void RemoteDebugger::MessageReceived(i::SmartPointer message) { + RemoteDebuggerEvent* event = + new RemoteDebuggerEvent(RemoteDebuggerEvent::kMessage, message); + AddEvent(event); +} + + +void RemoteDebugger::KeyboardCommand(i::SmartPointer command) { + RemoteDebuggerEvent* event = + new RemoteDebuggerEvent(RemoteDebuggerEvent::kKeyboard, command); + AddEvent(event); +} + + +void RemoteDebugger::ConnectionClosed() { + RemoteDebuggerEvent* event = + new RemoteDebuggerEvent(RemoteDebuggerEvent::kDisconnect, + i::SmartPointer()); + AddEvent(event); +} + + +void RemoteDebugger::AddEvent(RemoteDebuggerEvent* event) { + i::ScopedLock lock(event_access_); + if (head_ == NULL) { + ASSERT(tail_ == NULL); + head_ = event; + tail_ = event; + } else { + ASSERT(tail_ != NULL); + tail_->set_next(event); + tail_ = event; + } + event_available_->Signal(); +} + + +RemoteDebuggerEvent* RemoteDebugger::GetEvent() { + i::ScopedLock lock(event_access_); + ASSERT(head_ != NULL); + RemoteDebuggerEvent* result = head_; + head_ = head_->next(); + if (head_ == NULL) { + ASSERT(tail_ == result); + tail_ = NULL; + } + return result; +} + + +void RemoteDebugger::HandleMessageReceived(char* message) { + HandleScope scope; + + // Print the event details. + TryCatch try_catch; + Handle details = + Shell::DebugMessageDetails(Handle::Cast(String::New(message))); + if (try_catch.HasCaught()) { + Shell::ReportException(&try_catch); + return; + } + String::Utf8Value str(details->Get(String::New("text"))); + if (str.length() == 0) { + // Empty string is used to signal not to process this event. + return; + } + if (*str != NULL) { + printf("%s\n", *str); + } else { + printf("???\n"); + } + printf("dbg> "); +} + + +void RemoteDebugger::HandleKeyboardCommand(char* command) { + HandleScope scope; + + // Convert the debugger command to a JSON debugger request. + TryCatch try_catch; + Handle request = + Shell::DebugCommandToJSONRequest(String::New(command)); + if (try_catch.HasCaught()) { + Shell::ReportException(&try_catch); + return; + } + + // If undefined is returned the command was handled internally and there is + // no JSON to send. + if (request->IsUndefined()) { + return; + } + + // Send the JSON debugger request. + i::DebuggerAgentUtil::SendMessage(conn_, Handle::Cast(request)); +} + + +void ReceiverThread::Run() { + while (true) { + // Receive a message. + i::SmartPointer message = + i::DebuggerAgentUtil::ReceiveMessage(remote_debugger_->conn()); + if (*message == NULL) { + remote_debugger_->ConnectionClosed(); + return; + } + + // Pass the message to the main thread. + remote_debugger_->MessageReceived(message); + } +} + + +void KeyboardThread::Run() { + static const int kBufferSize = 256; + while (true) { + // read keyboard input. + char command[kBufferSize]; + char* str = fgets(command, kBufferSize, stdin); + if (str == NULL) { + break; + } + + // Pass the keyboard command to the main thread. + remote_debugger_->KeyboardCommand( + i::SmartPointer(i::OS::StrDup(command))); + } +} + + } // namespace v8 diff --git a/src/d8-debug.h b/src/d8-debug.h index e6d66be5b5..c7acc2f79f 100644 --- a/src/d8-debug.h +++ b/src/d8-debug.h @@ -41,6 +41,113 @@ void HandleDebugEvent(DebugEvent event, Handle event_data, Handle data); +// Start the remove debugger connecting to a V8 debugger agent on the specified +// port. +void RunRemoteDebugger(int port); + +// Forward declerations. +class RemoteDebuggerEvent; +class ReceiverThread; + + +// Remote debugging class. +class RemoteDebugger { + public: + explicit RemoteDebugger(int port) + : port_(port), + event_access_(i::OS::CreateMutex()), + event_available_(i::OS::CreateSemaphore(0)), + head_(NULL), tail_(NULL) {} + void Run(); + + // Handle events from the subordinate threads. + void MessageReceived(i::SmartPointer message); + void KeyboardCommand(i::SmartPointer command); + void ConnectionClosed(); + + private: + // Add new debugger event to the list. + void AddEvent(RemoteDebuggerEvent* event); + // Read next debugger event from the list. + RemoteDebuggerEvent* GetEvent(); + + // Handle a message from the debugged V8. + void HandleMessageReceived(char* message); + // Handle a keyboard command. + void HandleKeyboardCommand(char* command); + + // Get connection to agent in debugged V8. + i::Socket* conn() { return conn_; } + + int port_; // Port used to connect to debugger V8. + i::Socket* conn_; // Connection to debugger agent in debugged V8. + + // Linked list of events from debugged V8 and from keyboard input. Access to + // the list is guarded by a mutex and a semaphore signals new items in the + // list. + i::Mutex* event_access_; + i::Semaphore* event_available_; + RemoteDebuggerEvent* head_; + RemoteDebuggerEvent* tail_; + + friend class ReceiverThread; +}; + + +// Thread reading from debugged V8 instance. +class ReceiverThread: public i::Thread { + public: + explicit ReceiverThread(RemoteDebugger* remote_debugger) + : remote_debugger_(remote_debugger) {} + ~ReceiverThread() {} + + void Run(); + + private: + RemoteDebugger* remote_debugger_; +}; + + +// Thread reading keyboard input. +class KeyboardThread: public i::Thread { + public: + explicit KeyboardThread(RemoteDebugger* remote_debugger) + : remote_debugger_(remote_debugger) {} + ~KeyboardThread() {} + + void Run(); + + private: + RemoteDebugger* remote_debugger_; +}; + + +// Events processed by the main deubgger thread. +class RemoteDebuggerEvent { + public: + RemoteDebuggerEvent(int type, i::SmartPointer data) + : type_(type), data_(data), next_(NULL) { + ASSERT(type == kMessage || type == kKeyboard || type == kDisconnect); + } + + static const int kMessage = 1; + static const int kKeyboard = 2; + static const int kDisconnect = 3; + + int type() { return type_; } + char* data() { return *data_; } + + private: + void set_next(RemoteDebuggerEvent* event) { next_ = event; } + RemoteDebuggerEvent* next() { return next_; } + + int type_; + i::SmartPointer data_; + RemoteDebuggerEvent* next_; + + friend class RemoteDebugger; +}; + } // namespace v8 diff --git a/src/d8.cc b/src/d8.cc index 4a6630596c..0d939bb3f5 100644 --- a/src/d8.cc +++ b/src/d8.cc @@ -574,16 +574,22 @@ int Shell::Main(int argc, char* argv[]) { } } + // Run the remote debugger if requested. + if (i::FLAG_remote_debugger) { + RunRemoteDebugger(i::FLAG_debugger_port); + return 0; + } + // Start the debugger agent if requested. if (i::FLAG_debugger_agent) { v8::Debug::EnableAgent(i::FLAG_debugger_port); } // Start the in-process debugger if requested. - if (i::FLAG_debugger && !i::FLAG_debugger_agent) + if (i::FLAG_debugger && !i::FLAG_debugger_agent) { v8::Debug::SetDebugEventListener(HandleDebugEvent); + } } - if (run_shell) RunShell(); for (int i = 0; i < threads.length(); i++) { diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 02395062b6..cc94afe148 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -226,6 +226,8 @@ DEFINE_string(testing_serialization_file, "/tmp/serdes", DEFINE_bool(help, false, "Print usage message, including flags, on console") DEFINE_bool(dump_counters, false, "Dump counters on exit") DEFINE_bool(debugger, true, "Enable JavaScript debugger") +DEFINE_bool(remote_debugger, false, "Connect JavaScript debugger to the " + "debugger agent in another process") DEFINE_bool(debugger_agent, false, "Enable debugger agent") DEFINE_int(debugger_port, 5858, "Port to use for remote debugging") DEFINE_string(map_counters, false, "Map counters to a file")