• 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   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.
Run()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.
Terminate()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>
Append(base::Semaphore * ready_semaphore,Functor && task)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:
GetNext()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 
GdbServer()108 GdbServer::GdbServer() : has_module_list_changed_(false) {
109   task_runner_ = std::make_unique<TaskRunner>();
110 }
111 
112 template <typename Functor>
RunSyncTask(Functor && callback) const113 auto 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
Create()121 std::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 
~GdbServer()137 GdbServer::~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 
RunMessageLoopOnPause()148 void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); }
149 
QuitMessageLoopOnPause()150 void GdbServer::QuitMessageLoopOnPause() { task_runner_->Terminate(); }
151 
GetLoadedModules(bool clear_module_list_changed_flag)152 std::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 
GetModuleDebugHandler(uint32_t module_id,WasmModuleDebug ** wasm_module_debug)170 bool 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 
GetWasmGlobal(uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)182 bool 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 
GetWasmLocal(uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)196 bool 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 
GetWasmStackValue(uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)210 bool 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 
GetWasmMemory(uint32_t module_id,uint32_t offset,uint8_t * buffer,uint32_t size)224 uint32_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 
GetWasmData(uint32_t module_id,uint32_t offset,uint8_t * buffer,uint32_t size)239 uint32_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 
GetWasmModuleBytes(wasm_addr_t wasm_addr,uint8_t * buffer,uint32_t size)254 uint32_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 
AddBreakpoint(uint32_t wasm_module_id,uint32_t offset)268 bool 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 
RemoveBreakpoint(uint32_t wasm_module_id,uint32_t offset)285 bool 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 
GetWasmCallStack() const306 std::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 
AddIsolate(Isolate * isolate)316 void 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 
RemoveIsolate(Isolate * isolate)324 void 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 
Suspend()340 void 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 
PrepareStep()359 void 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 
AddWasmModule(uint32_t module_id,Local<debug::WasmScript> wasm_script)371 void 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 
GetTarget() const386 Target& GdbServer::GetTarget() const { return thread_->GetTarget(); }
387 
388 // static
389 std::atomic<uint32_t> GdbServer::DebugDelegate::id_s;
390 
DebugDelegate(Isolate * isolate,GdbServer * gdb_server)391 GdbServer::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 
~DebugDelegate()403 GdbServer::DebugDelegate::~DebugDelegate() {
404   // Deregister the delegate
405   isolate_->debug()->SetDebugDelegate(nullptr);
406 }
407 
ScriptCompiled(Local<debug::Script> script,bool is_live_edited,bool has_compile_error)408 void 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 
BreakProgramRequested(Local<v8::Context> paused_context,const std::vector<debug::BreakpointId> & inspector_break_points_hit,v8::debug::BreakReasons break_reasons)419 void 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 
ExceptionThrown(Local<v8::Context> paused_context,Local<Value> exception,Local<Value> promise,bool is_uncaught,debug::ExceptionType exception_type)429 void 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 
IsFunctionBlackboxed(Local<debug::Script> script,const debug::Location & start,const debug::Location & end)441 bool 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