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