| // Copyright 2008 the V8 project authors. All rights reserved. |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following |
| // disclaimer in the documentation and/or other materials provided |
| // with the distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived |
| // from this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| #include "d8.h" |
| #include "d8-debug.h" |
| #include "platform.h" |
| #include "debug-agent.h" |
| |
| |
| namespace v8 { |
| |
| static bool was_running = true; |
| |
| void PrintPrompt(bool is_running) { |
| const char* prompt = is_running? "> " : "dbg> "; |
| was_running = is_running; |
| printf("%s", prompt); |
| fflush(stdout); |
| } |
| |
| |
| void PrintPrompt() { |
| PrintPrompt(was_running); |
| } |
| |
| |
| void HandleDebugEvent(DebugEvent event, |
| Handle<Object> exec_state, |
| Handle<Object> event_data, |
| Handle<Value> data) { |
| HandleScope scope; |
| |
| // Check for handled event. |
| if (event != Break && event != Exception && event != AfterCompile) { |
| return; |
| } |
| |
| TryCatch try_catch; |
| |
| // Get the toJSONProtocol function on the event and get the JSON format. |
| Local<String> to_json_fun_name = String::New("toJSONProtocol"); |
| Local<Function> to_json_fun = |
| Function::Cast(*event_data->Get(to_json_fun_name)); |
| Local<Value> event_json = to_json_fun->Call(event_data, 0, NULL); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| return; |
| } |
| |
| // Print the event details. |
| Handle<Object> details = |
| Shell::DebugMessageDetails(Handle<String>::Cast(event_json)); |
| 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; |
| } |
| printf("%s\n", *str); |
| |
| // Get the debug command processor. |
| Local<String> fun_name = String::New("debugCommandProcessor"); |
| Local<Function> fun = Function::Cast(*exec_state->Get(fun_name)); |
| Local<Object> cmd_processor = |
| Object::Cast(*fun->Call(exec_state, 0, NULL)); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| return; |
| } |
| |
| static const int kBufferSize = 256; |
| bool running = false; |
| while (!running) { |
| char command[kBufferSize]; |
| PrintPrompt(running); |
| char* str = fgets(command, kBufferSize, stdin); |
| if (str == NULL) break; |
| |
| // Ignore empty commands. |
| if (strlen(command) == 0) continue; |
| |
| TryCatch try_catch; |
| |
| // Convert the debugger command to a JSON debugger request. |
| Handle<Value> request = |
| Shell::DebugCommandToJSONRequest(String::New(command)); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| continue; |
| } |
| |
| // If undefined is returned the command was handled internally and there is |
| // no JSON to send. |
| if (request->IsUndefined()) { |
| continue; |
| } |
| |
| Handle<String> fun_name; |
| Handle<Function> fun; |
| // All the functions used below take one argument. |
| static const int kArgc = 1; |
| Handle<Value> args[kArgc]; |
| |
| // Invoke the JavaScript to convert the debug command line to a JSON |
| // request, invoke the JSON request and convert the JSON respose to a text |
| // representation. |
| fun_name = String::New("processDebugRequest"); |
| fun = Handle<Function>::Cast(cmd_processor->Get(fun_name)); |
| args[0] = request; |
| Handle<Value> response_val = fun->Call(cmd_processor, kArgc, args); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| continue; |
| } |
| Handle<String> response = Handle<String>::Cast(response_val); |
| |
| // Convert the debugger response into text details and the running state. |
| Handle<Object> response_details = Shell::DebugMessageDetails(response); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| continue; |
| } |
| String::Utf8Value text_str(response_details->Get(String::New("text"))); |
| if (text_str.length() > 0) { |
| printf("%s\n", *text_str); |
| } |
| running = |
| response_details->Get(String::New("running"))->ToBoolean()->Value(); |
| } |
| } |
| |
| |
| void RunRemoteDebugger(int port) { |
| RemoteDebugger debugger(i::Isolate::Current(), 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<char>(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(isolate_, this); |
| receiver.Start(); |
| |
| // Start the keyboard thread. |
| KeyboardThread keyboard(isolate_, this); |
| keyboard.Start(); |
| PrintPrompt(); |
| |
| // 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<char> message) { |
| RemoteDebuggerEvent* event = |
| new RemoteDebuggerEvent(RemoteDebuggerEvent::kMessage, message); |
| AddEvent(event); |
| } |
| |
| |
| void RemoteDebugger::KeyboardCommand(i::SmartPointer<char> command) { |
| RemoteDebuggerEvent* event = |
| new RemoteDebuggerEvent(RemoteDebuggerEvent::kKeyboard, command); |
| AddEvent(event); |
| } |
| |
| |
| void RemoteDebugger::ConnectionClosed() { |
| RemoteDebuggerEvent* event = |
| new RemoteDebuggerEvent(RemoteDebuggerEvent::kDisconnect, |
| i::SmartPointer<char>()); |
| 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<Object> details = |
| Shell::DebugMessageDetails(Handle<String>::Cast(String::New(message))); |
| if (try_catch.HasCaught()) { |
| Shell::ReportException(&try_catch); |
| PrintPrompt(); |
| 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"); |
| } |
| |
| bool is_running = details->Get(String::New("running"))->ToBoolean()->Value(); |
| PrintPrompt(is_running); |
| } |
| |
| |
| void RemoteDebugger::HandleKeyboardCommand(char* command) { |
| HandleScope scope; |
| |
| // Convert the debugger command to a JSON debugger request. |
| TryCatch try_catch; |
| Handle<Value> request = |
| Shell::DebugCommandToJSONRequest(String::New(command)); |
| if (try_catch.HasCaught()) { |
| v8::String::Utf8Value exception(try_catch.Exception()); |
| const char* exception_string = Shell::ToCString(exception); |
| printf("%s\n", exception_string); |
| PrintPrompt(); |
| return; |
| } |
| |
| // If undefined is returned the command was handled internally and there is |
| // no JSON to send. |
| if (request->IsUndefined()) { |
| PrintPrompt(); |
| return; |
| } |
| |
| // Send the JSON debugger request. |
| i::DebuggerAgentUtil::SendMessage(conn_, Handle<String>::Cast(request)); |
| } |
| |
| |
| void ReceiverThread::Run() { |
| // Receive the connect message (with empty body). |
| i::SmartPointer<char> message = |
| i::DebuggerAgentUtil::ReceiveMessage(remote_debugger_->conn()); |
| ASSERT(*message == NULL); |
| |
| while (true) { |
| // Receive a message. |
| i::SmartPointer<char> 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<char>(i::StrDup(command))); |
| } |
| } |
| |
| |
| } // namespace v8 |