1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/debug/wasm/gdb-server/gdb-server.h"
6
7#include <inttypes.h>
8#include <functional>
9#include "src/api/api-inl.h"
10#include "src/api/api.h"
11#include "src/debug/debug.h"
12#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
13#include "src/utils/locked-queue-inl.h"
14
15namespace v8 {
16namespace internal {
17namespace wasm {
18namespace gdb_server {
19
20static const uint32_t kMaxWasmCallStack = 20;
21
22// A TaskRunner is an object that runs posted tasks (in the form of closure
23// objects). Tasks are queued and run, in order, in the thread where the
24// TaskRunner::RunMessageLoop() is called.
25class TaskRunner {
26 public:
27  // Class Task wraps a std::function with a semaphore to signal its completion.
28  // This logic would be neatly implemented with std::packaged_tasks but we
29  // cannot use <future> in V8.
30  class Task {
31   public:
32    Task(base::Semaphore* ready_semaphore, std::function<void()> func)
33        : ready_semaphore_(ready_semaphore), func_(func) {}
34
35    void Run() {
36      func_();
37      ready_semaphore_->Signal();
38    }
39
40    // A semaphore object passed by the thread that posts a task.
41    // The sender can Wait on this semaphore to block until the task has
42    // completed execution in the TaskRunner thread.
43    base::Semaphore* ready_semaphore_;
44
45    // The function to run.
46    std::function<void()> func_;
47  };
48
49  TaskRunner()
50      : process_queue_semaphore_(0),
51        nested_loop_count_(0),
52        is_terminated_(false) {}
53
54  TaskRunner(const TaskRunner&) = delete;
55  TaskRunner& operator=(const TaskRunner&) = delete;
56
57  // Starts the task runner. All tasks posted are run, in order, in the thread
58  // that calls this function.
59  void Run() {
60    is_terminated_ = false;
61    int loop_number = ++nested_loop_count_;
62    while (nested_loop_count_ == loop_number && !is_terminated_) {
63      std::shared_ptr<Task> task = GetNext();
64      if (task) {
65        task->Run();
66      }
67    }
68  }
69
70  // Terminates the task runner. Tasks that are still pending in the queue are
71  // not discarded and will be executed when the task runner is restarted.
72  void Terminate() {
73    DCHECK_LT(0, nested_loop_count_);
74    --nested_loop_count_;
75
76    is_terminated_ = true;
77    process_queue_semaphore_.Signal();
78  }
79
80  // Posts a task to the task runner, to be executed in the task runner thread.
81  template <typename Functor>
82  auto Append(base::Semaphore* ready_semaphore, Functor&& task) {
83    queue_.Enqueue(std::make_shared<Task>(ready_semaphore, task));
84    process_queue_semaphore_.Signal();
85  }
86
87 private:
88  std::shared_ptr<Task> GetNext() {
89    while (!is_terminated_) {
90      if (queue_.IsEmpty()) {
91        process_queue_semaphore_.Wait();
92      }
93
94      std::shared_ptr<Task> task;
95      if (queue_.Dequeue(&task)) {
96        return task;
97      }
98    }
99    return nullptr;
100  }
101
102  LockedQueue<std::shared_ptr<Task>> queue_;
103  v8::base::Semaphore process_queue_semaphore_;
104  int nested_loop_count_;
105  std::atomic<bool> is_terminated_;
106};
107
108GdbServer::GdbServer() : has_module_list_changed_(false) {
109  task_runner_ = std::make_unique<TaskRunner>();
110}
111
112template <typename Functor>
113auto GdbServer::RunSyncTask(Functor&& callback) const {
114  // Executed in the GDBServerThread.
115  v8::base::Semaphore ready_semaphore(0);
116  task_runner_->Append(&ready_semaphore, callback);
117  ready_semaphore.Wait();
118}
119
120// static
121std::unique_ptr<GdbServer> GdbServer::Create() {
122  DCHECK(FLAG_wasm_gdb_remote);
123
124  std::unique_ptr<GdbServer> gdb_server(new GdbServer());
125
126  // Spawns the GDB-stub thread where all the communication with the debugger
127  // happens.
128  gdb_server->thread_ = std::make_unique<GdbServerThread>(gdb_server.get());
129  if (!gdb_server->thread_->StartAndInitialize()) {
130    TRACE_GDB_REMOTE(
131        "Cannot initialize thread, GDB-remote debugging will be disabled.\n");
132    return nullptr;
133  }
134  return gdb_server;
135}
136
137GdbServer::~GdbServer() {
138  // All Isolates have been deregistered.
139  DCHECK(isolate_delegates_.empty());
140
141  if (thread_) {
142    // Waits for the GDB-stub thread to terminate.
143    thread_->Stop();
144    thread_->Join();
145  }
146}
147
148void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); }
149
150void GdbServer::QuitMessageLoopOnPause() { task_runner_->Terminate(); }
151
152std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules(
153    bool clear_module_list_changed_flag) {
154  // Executed in the GDBServerThread.
155  std::vector<GdbServer::WasmModuleInfo> modules;
156
157  RunSyncTask([this, &modules, clear_module_list_changed_flag]() {
158    // Executed in the isolate thread.
159    for (const auto& pair : scripts_) {
160      uint32_t module_id = pair.first;
161      const WasmModuleDebug& module_debug = pair.second;
162      modules.push_back({module_id, module_debug.GetModuleName()});
163    }
164
165    if (clear_module_list_changed_flag) has_module_list_changed_ = false;
166  });
167  return modules;
168}
169
170bool GdbServer::GetModuleDebugHandler(uint32_t module_id,
171                                      WasmModuleDebug** wasm_module_debug) {
172  // Always executed in the isolate thread.
173  ScriptsMap::iterator scriptIterator = scripts_.find(module_id);
174  if (scriptIterator != scripts_.end()) {
175    *wasm_module_debug = &scriptIterator->second;
176    return true;
177  }
178  wasm_module_debug = nullptr;
179  return false;
180}
181
182bool GdbServer::GetWasmGlobal(uint32_t frame_index, uint32_t index,
183                              uint8_t* buffer, uint32_t buffer_size,
184                              uint32_t* size) {
185  // Executed in the GDBServerThread.
186  bool result = false;
187  RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
188    // Executed in the isolate thread.
189    result = WasmModuleDebug::GetWasmGlobal(GetTarget().GetCurrentIsolate(),
190                                            frame_index, index, buffer,
191                                            buffer_size, size);
192  });
193  return result;
194}
195
196bool GdbServer::GetWasmLocal(uint32_t frame_index, uint32_t index,
197                             uint8_t* buffer, uint32_t buffer_size,
198                             uint32_t* size) {
199  // Executed in the GDBServerThread.
200  bool result = false;
201  RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
202    // Executed in the isolate thread.
203    result = WasmModuleDebug::GetWasmLocal(GetTarget().GetCurrentIsolate(),
204                                           frame_index, index, buffer,
205                                           buffer_size, size);
206  });
207  return result;
208}
209
210bool GdbServer::GetWasmStackValue(uint32_t frame_index, uint32_t index,
211                                  uint8_t* buffer, uint32_t buffer_size,
212                                  uint32_t* size) {
213  // Executed in the GDBServerThread.
214  bool result = false;
215  RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
216    // Executed in the isolate thread.
217    result = WasmModuleDebug::GetWasmStackValue(GetTarget().GetCurrentIsolate(),
218                                                frame_index, index, buffer,
219                                                buffer_size, size);
220  });
221  return result;
222}
223
224uint32_t GdbServer::GetWasmMemory(uint32_t module_id, uint32_t offset,
225                                  uint8_t* buffer, uint32_t size) {
226  // Executed in the GDBServerThread.
227  uint32_t bytes_read = 0;
228  RunSyncTask([this, &bytes_read, module_id, offset, buffer, size]() {
229    // Executed in the isolate thread.
230    WasmModuleDebug* module_debug = nullptr;
231    if (GetModuleDebugHandler(module_id, &module_debug)) {
232      bytes_read = module_debug->GetWasmMemory(GetTarget().GetCurrentIsolate(),
233                                               offset, buffer, size);
234    }
235  });
236  return bytes_read;
237}
238
239uint32_t GdbServer::GetWasmData(uint32_t module_id, uint32_t offset,
240                                uint8_t* buffer, uint32_t size) {
241  // Executed in the GDBServerThread.
242  uint32_t bytes_read = 0;
243  RunSyncTask([this, &bytes_read, module_id, offset, buffer, size]() {
244    // Executed in the isolate thread.
245    WasmModuleDebug* module_debug = nullptr;
246    if (GetModuleDebugHandler(module_id, &module_debug)) {
247      bytes_read = module_debug->GetWasmData(GetTarget().GetCurrentIsolate(),
248                                             offset, buffer, size);
249    }
250  });
251  return bytes_read;
252}
253
254uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
255                                       uint32_t size) {
256  // Executed in the GDBServerThread.
257  uint32_t bytes_read = 0;
258  RunSyncTask([this, &bytes_read, wasm_addr, buffer, size]() {
259    // Executed in the isolate thread.
260    WasmModuleDebug* module_debug;
261    if (GetModuleDebugHandler(wasm_addr.ModuleId(), &module_debug)) {
262      bytes_read = module_debug->GetWasmModuleBytes(wasm_addr, buffer, size);
263    }
264  });
265  return bytes_read;
266}
267
268bool GdbServer::AddBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
269  // Executed in the GDBServerThread.
270  bool result = false;
271  RunSyncTask([this, &result, wasm_module_id, offset]() {
272    // Executed in the isolate thread.
273    WasmModuleDebug* module_debug;
274    if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
275      int breakpoint_id = 0;
276      if (module_debug->AddBreakpoint(offset, &breakpoint_id)) {
277        breakpoints_[wasm_addr_t(wasm_module_id, offset)] = breakpoint_id;
278        result = true;
279      }
280    }
281  });
282  return result;
283}
284
285bool GdbServer::RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
286  // Executed in the GDBServerThread.
287  bool result = false;
288  RunSyncTask([this, &result, wasm_module_id, offset]() {
289    // Executed in the isolate thread.
290    BreakpointsMap::iterator it =
291        breakpoints_.find(wasm_addr_t(wasm_module_id, offset));
292    if (it != breakpoints_.end()) {
293      int breakpoint_id = it->second;
294      breakpoints_.erase(it);
295
296      WasmModuleDebug* module_debug;
297      if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
298        module_debug->RemoveBreakpoint(offset, breakpoint_id);
299        result = true;
300      }
301    }
302  });
303  return result;
304}
305
306std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const {
307  // Executed in the GDBServerThread.
308  std::vector<wasm_addr_t> result;
309  RunSyncTask([this, &result]() {
310    // Executed in the isolate thread.
311    result = GetTarget().GetCallStack();
312  });
313  return result;
314}
315
316void GdbServer::AddIsolate(Isolate* isolate) {
317  // Executed in the isolate thread.
318  if (isolate_delegates_.find(isolate) == isolate_delegates_.end()) {
319    isolate_delegates_[isolate] =
320        std::make_unique<DebugDelegate>(isolate, this);
321  }
322}
323
324void GdbServer::RemoveIsolate(Isolate* isolate) {
325  // Executed in the isolate thread.
326  auto it = isolate_delegates_.find(isolate);
327  if (it != isolate_delegates_.end()) {
328    for (auto it = scripts_.begin(); it != scripts_.end();) {
329      if (it->second.GetIsolate() == isolate) {
330        it = scripts_.erase(it);
331        has_module_list_changed_ = true;
332      } else {
333        ++it;
334      }
335    }
336    isolate_delegates_.erase(it);
337  }
338}
339
340void GdbServer::Suspend() {
341  // Executed in the GDBServerThread.
342  auto it = isolate_delegates_.begin();
343  if (it != isolate_delegates_.end()) {
344    Isolate* isolate = it->first;
345    v8::Isolate* v8Isolate = (v8::Isolate*)isolate;
346    v8Isolate->RequestInterrupt(
347        // Executed in the isolate thread.
348        [](v8::Isolate* isolate, void*) {
349          if (v8::debug::AllFramesOnStackAreBlackboxed(isolate)) {
350            v8::debug::SetBreakOnNextFunctionCall(isolate);
351          } else {
352            v8::debug::BreakRightNow(isolate);
353          }
354        },
355        this);
356  }
357}
358
359void GdbServer::PrepareStep() {
360  // Executed in the GDBServerThread.
361  wasm_addr_t pc = GetTarget().GetCurrentPc();
362  RunSyncTask([this, pc]() {
363    // Executed in the isolate thread.
364    WasmModuleDebug* module_debug;
365    if (GetModuleDebugHandler(pc.ModuleId(), &module_debug)) {
366      module_debug->PrepareStep();
367    }
368  });
369}
370
371void GdbServer::AddWasmModule(uint32_t module_id,
372                              Local<debug::WasmScript> wasm_script) {
373  // Executed in the isolate thread.
374  DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
375  v8::Isolate* isolate = wasm_script->GetIsolate();
376  scripts_.insert(
377      std::make_pair(module_id, WasmModuleDebug(isolate, wasm_script)));
378  has_module_list_changed_ = true;
379
380  if (FLAG_wasm_pause_waiting_for_debugger && scripts_.size() == 1) {
381    TRACE_GDB_REMOTE("Paused, waiting for a debugger to attach...\n");
382    Suspend();
383  }
384}
385
386Target& GdbServer::GetTarget() const { return thread_->GetTarget(); }
387
388// static
389std::atomic<uint32_t> GdbServer::DebugDelegate::id_s;
390
391GdbServer::DebugDelegate::DebugDelegate(Isolate* isolate, GdbServer* gdb_server)
392    : isolate_(isolate), id_(id_s++), gdb_server_(gdb_server) {
393  isolate_->SetCaptureStackTraceForUncaughtExceptions(
394      true, kMaxWasmCallStack, v8::StackTrace::kOverview);
395
396  // Register the delegate
397  isolate_->debug()->SetDebugDelegate(this);
398  v8::debug::TierDownAllModulesPerIsolate((v8::Isolate*)isolate_);
399  v8::debug::ChangeBreakOnException((v8::Isolate*)isolate_,
400                                    v8::debug::BreakOnUncaughtException);
401}
402
403GdbServer::DebugDelegate::~DebugDelegate() {
404  // Deregister the delegate
405  isolate_->debug()->SetDebugDelegate(nullptr);
406}
407
408void GdbServer::DebugDelegate::ScriptCompiled(Local<debug::Script> script,
409                                              bool is_live_edited,
410                                              bool has_compile_error) {
411  // Executed in the isolate thread.
412  if (script->IsWasm()) {
413    DCHECK_EQ(reinterpret_cast<v8::Isolate*>(isolate_), script->GetIsolate());
414    gdb_server_->AddWasmModule(GetModuleId(script->Id()),
415                               script.As<debug::WasmScript>());
416  }
417}
418
419void GdbServer::DebugDelegate::BreakProgramRequested(
420    // Executed in the isolate thread.
421    Local<v8::Context> paused_context,
422    const std::vector<debug::BreakpointId>& inspector_break_points_hit,
423    v8::debug::BreakReasons break_reasons) {
424  gdb_server_->GetTarget().OnProgramBreak(
425      isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
426  gdb_server_->RunMessageLoopOnPause();
427}
428
429void GdbServer::DebugDelegate::ExceptionThrown(
430    // Executed in the isolate thread.
431    Local<v8::Context> paused_context, Local<Value> exception,
432    Local<Value> promise, bool is_uncaught,
433    debug::ExceptionType exception_type) {
434  if (exception_type == v8::debug::kException && is_uncaught) {
435    gdb_server_->GetTarget().OnException(
436        isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
437    gdb_server_->RunMessageLoopOnPause();
438  }
439}
440
441bool GdbServer::DebugDelegate::IsFunctionBlackboxed(
442    // Executed in the isolate thread.
443    Local<debug::Script> script, const debug::Location& start,
444    const debug::Location& end) {
445  return false;
446}
447
448}  // namespace gdb_server
449}  // namespace wasm
450}  // namespace internal
451}  // namespace v8
452