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