1// Copyright 2012 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6//     * Redistributions of source code must retain the above copyright
7//       notice, this list of conditions and the following disclaimer.
8//     * Redistributions in binary form must reproduce the above
9//       copyright notice, this list of conditions and the following
10//       disclaimer in the documentation and/or other materials provided
11//       with the distribution.
12//     * Neither the name of Google Inc. nor the names of its
13//       contributors may be used to endorse or promote products derived
14//       from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28#ifdef ENABLE_DEBUGGER_SUPPORT
29
30#include "d8.h"
31#include "d8-debug.h"
32#include "debug-agent.h"
33
34
35namespace v8 {
36
37static bool was_running = true;
38
39void PrintPrompt(bool is_running) {
40  const char* prompt = is_running? "> " : "dbg> ";
41  was_running = is_running;
42  printf("%s", prompt);
43  fflush(stdout);
44}
45
46
47void PrintPrompt() {
48  PrintPrompt(was_running);
49}
50
51
52void HandleDebugEvent(const Debug::EventDetails& event_details) {
53  // TODO(svenpanne) There should be a way to retrieve this in the callback.
54  Isolate* isolate = Isolate::GetCurrent();
55  HandleScope scope(isolate);
56
57  DebugEvent event = event_details.GetEvent();
58  // Check for handled event.
59  if (event != Break && event != Exception && event != AfterCompile) {
60    return;
61  }
62
63  TryCatch try_catch;
64
65  // Get the toJSONProtocol function on the event and get the JSON format.
66  Local<String> to_json_fun_name =
67      String::NewFromUtf8(isolate, "toJSONProtocol");
68  Handle<Object> event_data = event_details.GetEventData();
69  Local<Function> to_json_fun =
70      Local<Function>::Cast(event_data->Get(to_json_fun_name));
71  Local<Value> event_json = to_json_fun->Call(event_data, 0, NULL);
72  if (try_catch.HasCaught()) {
73    Shell::ReportException(isolate, &try_catch);
74    return;
75  }
76
77  // Print the event details.
78  Handle<Object> details =
79      Shell::DebugMessageDetails(isolate, Handle<String>::Cast(event_json));
80  if (try_catch.HasCaught()) {
81    Shell::ReportException(isolate, &try_catch);
82    return;
83  }
84  String::Utf8Value str(details->Get(String::NewFromUtf8(isolate, "text")));
85  if (str.length() == 0) {
86    // Empty string is used to signal not to process this event.
87    return;
88  }
89  printf("%s\n", *str);
90
91  // Get the debug command processor.
92  Local<String> fun_name =
93      String::NewFromUtf8(isolate, "debugCommandProcessor");
94  Handle<Object> exec_state = event_details.GetExecutionState();
95  Local<Function> fun = Local<Function>::Cast(exec_state->Get(fun_name));
96  Local<Object> cmd_processor =
97      Local<Object>::Cast(fun->Call(exec_state, 0, NULL));
98  if (try_catch.HasCaught()) {
99    Shell::ReportException(isolate, &try_catch);
100    return;
101  }
102
103  static const int kBufferSize = 256;
104  bool running = false;
105  while (!running) {
106    char command[kBufferSize];
107    PrintPrompt(running);
108    char* str = fgets(command, kBufferSize, stdin);
109    if (str == NULL) break;
110
111    // Ignore empty commands.
112    if (strlen(command) == 0) continue;
113
114    TryCatch try_catch;
115
116    // Convert the debugger command to a JSON debugger request.
117    Handle<Value> request = Shell::DebugCommandToJSONRequest(
118        isolate, String::NewFromUtf8(isolate, command));
119    if (try_catch.HasCaught()) {
120      Shell::ReportException(isolate, &try_catch);
121      continue;
122    }
123
124    // If undefined is returned the command was handled internally and there is
125    // no JSON to send.
126    if (request->IsUndefined()) {
127      continue;
128    }
129
130    Handle<String> fun_name;
131    Handle<Function> fun;
132    // All the functions used below take one argument.
133    static const int kArgc = 1;
134    Handle<Value> args[kArgc];
135
136    // Invoke the JavaScript to convert the debug command line to a JSON
137    // request, invoke the JSON request and convert the JSON respose to a text
138    // representation.
139    fun_name = String::NewFromUtf8(isolate, "processDebugRequest");
140    fun = Handle<Function>::Cast(cmd_processor->Get(fun_name));
141    args[0] = request;
142    Handle<Value> response_val = fun->Call(cmd_processor, kArgc, args);
143    if (try_catch.HasCaught()) {
144      Shell::ReportException(isolate, &try_catch);
145      continue;
146    }
147    Handle<String> response = Handle<String>::Cast(response_val);
148
149    // Convert the debugger response into text details and the running state.
150    Handle<Object> response_details =
151        Shell::DebugMessageDetails(isolate, response);
152    if (try_catch.HasCaught()) {
153      Shell::ReportException(isolate, &try_catch);
154      continue;
155    }
156    String::Utf8Value text_str(
157        response_details->Get(String::NewFromUtf8(isolate, "text")));
158    if (text_str.length() > 0) {
159      printf("%s\n", *text_str);
160    }
161    running = response_details->Get(String::NewFromUtf8(isolate, "running"))
162                  ->ToBoolean()
163                  ->Value();
164  }
165}
166
167
168void RunRemoteDebugger(Isolate* isolate, int port) {
169  RemoteDebugger debugger(isolate, port);
170  debugger.Run();
171}
172
173
174void RemoteDebugger::Run() {
175  bool ok;
176
177  // Connect to the debugger agent.
178  conn_ = new i::Socket;
179  static const int kPortStrSize = 6;
180  char port_str[kPortStrSize];
181  i::OS::SNPrintF(i::Vector<char>(port_str, kPortStrSize), "%d", port_);
182  ok = conn_->Connect("localhost", port_str);
183  if (!ok) {
184    printf("Unable to connect to debug agent %d\n", i::Socket::GetLastError());
185    return;
186  }
187
188  // Start the receiver thread.
189  ReceiverThread receiver(this);
190  receiver.Start();
191
192  // Start the keyboard thread.
193  KeyboardThread keyboard(this);
194  keyboard.Start();
195  PrintPrompt();
196
197  // Process events received from debugged VM and from the keyboard.
198  bool terminate = false;
199  while (!terminate) {
200    event_available_.Wait();
201    RemoteDebuggerEvent* event = GetEvent();
202    switch (event->type()) {
203      case RemoteDebuggerEvent::kMessage:
204        HandleMessageReceived(event->data());
205        break;
206      case RemoteDebuggerEvent::kKeyboard:
207        HandleKeyboardCommand(event->data());
208        break;
209      case RemoteDebuggerEvent::kDisconnect:
210        terminate = true;
211        break;
212
213      default:
214        UNREACHABLE();
215    }
216    delete event;
217  }
218
219  // Wait for the receiver thread to end.
220  receiver.Join();
221}
222
223
224void RemoteDebugger::MessageReceived(i::SmartArrayPointer<char> message) {
225  RemoteDebuggerEvent* event =
226      new RemoteDebuggerEvent(RemoteDebuggerEvent::kMessage, message);
227  AddEvent(event);
228}
229
230
231void RemoteDebugger::KeyboardCommand(i::SmartArrayPointer<char> command) {
232  RemoteDebuggerEvent* event =
233      new RemoteDebuggerEvent(RemoteDebuggerEvent::kKeyboard, command);
234  AddEvent(event);
235}
236
237
238void RemoteDebugger::ConnectionClosed() {
239  RemoteDebuggerEvent* event =
240      new RemoteDebuggerEvent(RemoteDebuggerEvent::kDisconnect,
241                              i::SmartArrayPointer<char>());
242  AddEvent(event);
243}
244
245
246void RemoteDebugger::AddEvent(RemoteDebuggerEvent* event) {
247  i::LockGuard<i::Mutex> lock_guard(&event_access_);
248  if (head_ == NULL) {
249    ASSERT(tail_ == NULL);
250    head_ = event;
251    tail_ = event;
252  } else {
253    ASSERT(tail_ != NULL);
254    tail_->set_next(event);
255    tail_ = event;
256  }
257  event_available_.Signal();
258}
259
260
261RemoteDebuggerEvent* RemoteDebugger::GetEvent() {
262  i::LockGuard<i::Mutex> lock_guard(&event_access_);
263  ASSERT(head_ != NULL);
264  RemoteDebuggerEvent* result = head_;
265  head_ = head_->next();
266  if (head_ == NULL) {
267    ASSERT(tail_ == result);
268    tail_ = NULL;
269  }
270  return result;
271}
272
273
274void RemoteDebugger::HandleMessageReceived(char* message) {
275  Locker lock(isolate_);
276  HandleScope scope(isolate_);
277
278  // Print the event details.
279  TryCatch try_catch;
280  Handle<Object> details = Shell::DebugMessageDetails(
281      isolate_, Handle<String>::Cast(String::NewFromUtf8(isolate_, message)));
282  if (try_catch.HasCaught()) {
283    Shell::ReportException(isolate_, &try_catch);
284    PrintPrompt();
285    return;
286  }
287  String::Utf8Value str(details->Get(String::NewFromUtf8(isolate_, "text")));
288  if (str.length() == 0) {
289    // Empty string is used to signal not to process this event.
290    return;
291  }
292  if (*str != NULL) {
293    printf("%s\n", *str);
294  } else {
295    printf("???\n");
296  }
297
298  bool is_running = details->Get(String::NewFromUtf8(isolate_, "running"))
299                        ->ToBoolean()
300                        ->Value();
301  PrintPrompt(is_running);
302}
303
304
305void RemoteDebugger::HandleKeyboardCommand(char* command) {
306  Locker lock(isolate_);
307  HandleScope scope(isolate_);
308
309  // Convert the debugger command to a JSON debugger request.
310  TryCatch try_catch;
311  Handle<Value> request = Shell::DebugCommandToJSONRequest(
312      isolate_, String::NewFromUtf8(isolate_, command));
313  if (try_catch.HasCaught()) {
314    Shell::ReportException(isolate_, &try_catch);
315    PrintPrompt();
316    return;
317  }
318
319  // If undefined is returned the command was handled internally and there is
320  // no JSON to send.
321  if (request->IsUndefined()) {
322    PrintPrompt();
323    return;
324  }
325
326  // Send the JSON debugger request.
327  i::DebuggerAgentUtil::SendMessage(conn_, Handle<String>::Cast(request));
328}
329
330
331void ReceiverThread::Run() {
332  // Receive the connect message (with empty body).
333  i::SmartArrayPointer<char> message =
334      i::DebuggerAgentUtil::ReceiveMessage(remote_debugger_->conn());
335  ASSERT(*message == NULL);
336
337  while (true) {
338    // Receive a message.
339    i::SmartArrayPointer<char> message =
340        i::DebuggerAgentUtil::ReceiveMessage(remote_debugger_->conn());
341    if (*message == NULL) {
342      remote_debugger_->ConnectionClosed();
343      return;
344    }
345
346    // Pass the message to the main thread.
347    remote_debugger_->MessageReceived(message);
348  }
349}
350
351
352void KeyboardThread::Run() {
353  static const int kBufferSize = 256;
354  while (true) {
355    // read keyboard input.
356    char command[kBufferSize];
357    char* str = fgets(command, kBufferSize, stdin);
358    if (str == NULL) {
359      break;
360    }
361
362    // Pass the keyboard command to the main thread.
363    remote_debugger_->KeyboardCommand(
364        i::SmartArrayPointer<char>(i::StrDup(command)));
365  }
366}
367
368
369}  // namespace v8
370
371#endif  // ENABLE_DEBUGGER_SUPPORT
372