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