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