diff options
Diffstat (limited to 'deps/v8/src/debug/wasm/gdb-server/gdb-server.cc')
-rw-r--r-- | deps/v8/src/debug/wasm/gdb-server/gdb-server.cc | 400 |
1 files changed, 393 insertions, 7 deletions
diff --git a/deps/v8/src/debug/wasm/gdb-server/gdb-server.cc b/deps/v8/src/debug/wasm/gdb-server/gdb-server.cc index bad7f439eb5..96e2308cee7 100644 --- a/deps/v8/src/debug/wasm/gdb-server/gdb-server.cc +++ b/deps/v8/src/debug/wasm/gdb-server/gdb-server.cc @@ -4,34 +4,420 @@ #include "src/debug/wasm/gdb-server/gdb-server.h" +#include <inttypes.h> +#include <functional> +#include "src/api/api-inl.h" +#include "src/api/api.h" +#include "src/debug/debug.h" #include "src/debug/wasm/gdb-server/gdb-server-thread.h" -#include "src/wasm/wasm-engine.h" +#include "src/utils/locked-queue-inl.h" namespace v8 { namespace internal { namespace wasm { namespace gdb_server { -GdbServer::GdbServer() { - DCHECK(!thread_); +static const uint32_t kMaxWasmCallStack = 20; + +// A TaskRunner is an object that runs posted tasks (in the form of closure +// objects). Tasks are queued and run, in order, in the thread where the +// TaskRunner::RunMessageLoop() is called. +class TaskRunner { + public: + // Class Task wraps a std::function with a semaphore to signal its completion. + // This logic would be neatly implemented with std::packaged_tasks but we + // cannot use <future> in V8. + class Task { + public: + Task(base::Semaphore* ready_semaphore, std::function<void()> func) + : ready_semaphore_(ready_semaphore), func_(func) {} + + void Run() { + func_(); + ready_semaphore_->Signal(); + } + + // A semaphore object passed by the thread that posts a task. + // The sender can Wait on this semaphore to block until the task has + // completed execution in the TaskRunner thread. + base::Semaphore* ready_semaphore_; + + // The function to run. + std::function<void()> func_; + }; + + TaskRunner() + : process_queue_semaphore_(0), + nested_loop_count_(0), + is_terminated_(false) {} + + // Starts the task runner. All tasks posted are run, in order, in the thread + // that calls this function. + void Run() { + is_terminated_ = false; + int loop_number = ++nested_loop_count_; + while (nested_loop_count_ == loop_number && !is_terminated_) { + std::shared_ptr<Task> task = GetNext(); + if (task) { + task->Run(); + } + } + } + + // Terminates the task runner. Tasks that are still pending in the queue are + // not discarded and will be executed when the task runner is restarted. + void Terminate() { + DCHECK_LT(0, nested_loop_count_); + --nested_loop_count_; + + is_terminated_ = true; + process_queue_semaphore_.Signal(); + } + + // Posts a task to the task runner, to be executed in the task runner thread. + template <typename Functor> + auto Append(base::Semaphore* ready_semaphore, Functor&& task) { + queue_.Enqueue(std::make_shared<Task>(ready_semaphore, task)); + process_queue_semaphore_.Signal(); + } + + private: + std::shared_ptr<Task> GetNext() { + while (!is_terminated_) { + if (queue_.IsEmpty()) { + process_queue_semaphore_.Wait(); + } + + std::shared_ptr<Task> task; + if (queue_.Dequeue(&task)) { + return task; + } + } + return nullptr; + } + + LockedQueue<std::shared_ptr<Task>> queue_; + v8::base::Semaphore process_queue_semaphore_; + int nested_loop_count_; + std::atomic<bool> is_terminated_; + + DISALLOW_COPY_AND_ASSIGN(TaskRunner); +}; + +GdbServer::GdbServer() { task_runner_ = std::make_unique<TaskRunner>(); } + +template <typename Functor> +auto GdbServer::RunSyncTask(Functor&& callback) const { + // Executed in the GDBServerThread. + v8::base::Semaphore ready_semaphore(0); + task_runner_->Append(&ready_semaphore, callback); + ready_semaphore.Wait(); +} + +// static +std::unique_ptr<GdbServer> GdbServer::Create() { DCHECK(FLAG_wasm_gdb_remote); - thread_ = std::make_unique<GdbServerThread>(this); - // TODO(paolosev): does StartSynchronously hurt performances? - if (!thread_->StartAndInitialize()) { + std::unique_ptr<GdbServer> gdb_server(new GdbServer()); + + // Spawns the GDB-stub thread where all the communication with the debugger + // happens. + gdb_server->thread_ = std::make_unique<GdbServerThread>(gdb_server.get()); + if (!gdb_server->thread_->StartAndInitialize()) { TRACE_GDB_REMOTE( "Cannot initialize thread, GDB-remote debugging will be disabled.\n"); - thread_ = nullptr; + return nullptr; } + return gdb_server; } GdbServer::~GdbServer() { + // All Isolates have been deregistered. + DCHECK(isolate_delegates_.empty()); + if (thread_) { + // Waits for the GDB-stub thread to terminate. thread_->Stop(); thread_->Join(); } } +void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); } + +void GdbServer::QuitMessageLoopOnPause() { task_runner_->Terminate(); } + +std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules() { + // Executed in the GDBServerThread. + std::vector<GdbServer::WasmModuleInfo> modules; + + RunSyncTask([this, &modules]() { + // Executed in the isolate thread. + for (const auto& pair : scripts_) { + uint32_t module_id = pair.first; + const WasmModuleDebug& module_debug = pair.second; + modules.push_back({module_id, module_debug.GetModuleName()}); + } + }); + return modules; +} + +bool GdbServer::GetModuleDebugHandler(uint32_t module_id, + WasmModuleDebug** wasm_module_debug) { + // Always executed in the isolate thread. + ScriptsMap::iterator scriptIterator = scripts_.find(module_id); + if (scriptIterator != scripts_.end()) { + *wasm_module_debug = &scriptIterator->second; + return true; + } + wasm_module_debug = nullptr; + return false; +} + +bool GdbServer::GetWasmGlobal(uint32_t frame_index, uint32_t index, + uint8_t* buffer, uint32_t buffer_size, + uint32_t* size) { + // Executed in the GDBServerThread. + bool result = false; + RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() { + // Executed in the isolate thread. + result = WasmModuleDebug::GetWasmGlobal(GetTarget().GetCurrentIsolate(), + frame_index, index, buffer, + buffer_size, size); + }); + return result; +} + +bool GdbServer::GetWasmLocal(uint32_t frame_index, uint32_t index, + uint8_t* buffer, uint32_t buffer_size, + uint32_t* size) { + // Executed in the GDBServerThread. + bool result = false; + RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() { + // Executed in the isolate thread. + result = WasmModuleDebug::GetWasmLocal(GetTarget().GetCurrentIsolate(), + frame_index, index, buffer, + buffer_size, size); + }); + return result; +} + +bool GdbServer::GetWasmStackValue(uint32_t frame_index, uint32_t index, + uint8_t* buffer, uint32_t buffer_size, + uint32_t* size) { + // Executed in the GDBServerThread. + bool result = false; + RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() { + // Executed in the isolate thread. + result = WasmModuleDebug::GetWasmStackValue(GetTarget().GetCurrentIsolate(), + frame_index, index, buffer, + buffer_size, size); + }); + return result; +} + +uint32_t GdbServer::GetWasmMemory(uint32_t frame_index, uint32_t offset, + uint8_t* buffer, uint32_t size) { + // Executed in the GDBServerThread. + uint32_t bytes_read = 0; + RunSyncTask([this, &bytes_read, frame_index, offset, buffer, size]() { + // Executed in the isolate thread. + bytes_read = WasmModuleDebug::GetWasmMemory( + GetTarget().GetCurrentIsolate(), frame_index, offset, buffer, size); + }); + return bytes_read; +} + +uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer, + uint32_t size) { + // Executed in the GDBServerThread. + uint32_t bytes_read = 0; + RunSyncTask([this, &bytes_read, wasm_addr, buffer, size]() { + // Executed in the isolate thread. + WasmModuleDebug* module_debug; + if (GetModuleDebugHandler(wasm_addr.ModuleId(), &module_debug)) { + bytes_read = module_debug->GetWasmModuleBytes(wasm_addr, buffer, size); + } + }); + return bytes_read; +} + +bool GdbServer::AddBreakpoint(uint32_t wasm_module_id, uint32_t offset) { + // Executed in the GDBServerThread. + bool result = false; + RunSyncTask([this, &result, wasm_module_id, offset]() { + // Executed in the isolate thread. + WasmModuleDebug* module_debug; + if (GetModuleDebugHandler(wasm_module_id, &module_debug)) { + int breakpoint_id = 0; + if (module_debug->AddBreakpoint(offset, &breakpoint_id)) { + breakpoints_[wasm_addr_t(wasm_module_id, offset)] = breakpoint_id; + result = true; + } + } + }); + return result; +} + +bool GdbServer::RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset) { + // Executed in the GDBServerThread. + bool result = false; + RunSyncTask([this, &result, wasm_module_id, offset]() { + // Executed in the isolate thread. + BreakpointsMap::iterator it = + breakpoints_.find(wasm_addr_t(wasm_module_id, offset)); + if (it != breakpoints_.end()) { + int breakpoint_id = it->second; + breakpoints_.erase(it); + + WasmModuleDebug* module_debug; + if (GetModuleDebugHandler(wasm_module_id, &module_debug)) { + module_debug->RemoveBreakpoint(offset, breakpoint_id); + result = true; + } + } + }); + return result; +} + +std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const { + // Executed in the GDBServerThread. + std::vector<wasm_addr_t> result; + RunSyncTask([this, &result]() { + // Executed in the isolate thread. + result = GetTarget().GetCallStack(); + }); + return result; +} + +void GdbServer::AddIsolate(Isolate* isolate) { + // Executed in the isolate thread. + if (isolate_delegates_.find(isolate) == isolate_delegates_.end()) { + isolate_delegates_[isolate] = + std::make_unique<DebugDelegate>(isolate, this); + } +} + +void GdbServer::RemoveIsolate(Isolate* isolate) { + // Executed in the isolate thread. + auto it = isolate_delegates_.find(isolate); + if (it != isolate_delegates_.end()) { + for (auto it = scripts_.begin(); it != scripts_.end();) { + if (it->second.GetIsolate() == isolate) { + it = scripts_.erase(it); + } else { + ++it; + } + } + isolate_delegates_.erase(it); + } +} + +void GdbServer::Suspend() { + // Executed in the GDBServerThread. + auto it = isolate_delegates_.begin(); + if (it != isolate_delegates_.end()) { + Isolate* isolate = it->first; + v8::Isolate* v8Isolate = (v8::Isolate*)isolate; + v8Isolate->RequestInterrupt( + // Executed in the isolate thread. + [](v8::Isolate* isolate, void*) { + if (v8::debug::AllFramesOnStackAreBlackboxed(isolate)) { + v8::debug::SetBreakOnNextFunctionCall(isolate); + } else { + v8::debug::BreakRightNow(isolate); + } + }, + this); + } +} + +void GdbServer::PrepareStep() { + // Executed in the GDBServerThread. + wasm_addr_t pc = GetTarget().GetCurrentPc(); + RunSyncTask([this, pc]() { + // Executed in the isolate thread. + WasmModuleDebug* module_debug; + if (GetModuleDebugHandler(pc.ModuleId(), &module_debug)) { + module_debug->PrepareStep(); + } + }); +} + +void GdbServer::AddWasmModule(uint32_t module_id, + Local<debug::WasmScript> wasm_script) { + // Executed in the isolate thread. + DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type()); + v8::Isolate* isolate = wasm_script->GetIsolate(); + scripts_.insert( + std::make_pair(module_id, WasmModuleDebug(isolate, wasm_script))); + + if (FLAG_wasm_pause_waiting_for_debugger && scripts_.size() == 1) { + TRACE_GDB_REMOTE("Paused, waiting for a debugger to attach...\n"); + Suspend(); + } +} + +Target& GdbServer::GetTarget() const { return thread_->GetTarget(); } + +// static +std::atomic<uint32_t> GdbServer::DebugDelegate::id_s; + +GdbServer::DebugDelegate::DebugDelegate(Isolate* isolate, GdbServer* gdb_server) + : isolate_(isolate), id_(id_s++), gdb_server_(gdb_server) { + isolate_->SetCaptureStackTraceForUncaughtExceptions( + true, kMaxWasmCallStack, v8::StackTrace::kOverview); + + // Register the delegate + isolate_->debug()->SetDebugDelegate(this); + v8::debug::TierDownAllModulesPerIsolate((v8::Isolate*)isolate_); + v8::debug::ChangeBreakOnException((v8::Isolate*)isolate_, + v8::debug::BreakOnUncaughtException); +} + +GdbServer::DebugDelegate::~DebugDelegate() { + // Deregister the delegate + isolate_->debug()->SetDebugDelegate(nullptr); +} + +void GdbServer::DebugDelegate::ScriptCompiled(Local<debug::Script> script, + bool is_live_edited, + bool has_compile_error) { + // Executed in the isolate thread. + if (script->IsWasm()) { + DCHECK_EQ(reinterpret_cast<v8::Isolate*>(isolate_), script->GetIsolate()); + gdb_server_->AddWasmModule(GetModuleId(script->Id()), + script.As<debug::WasmScript>()); + } +} + +void GdbServer::DebugDelegate::BreakProgramRequested( + // Executed in the isolate thread. + Local<v8::Context> paused_context, + const std::vector<debug::BreakpointId>& inspector_break_points_hit) { + gdb_server_->GetTarget().OnProgramBreak( + isolate_, WasmModuleDebug::GetCallStack(id_, isolate_)); + gdb_server_->RunMessageLoopOnPause(); +} + +void GdbServer::DebugDelegate::ExceptionThrown( + // Executed in the isolate thread. + Local<v8::Context> paused_context, Local<Value> exception, + Local<Value> promise, bool is_uncaught, + debug::ExceptionType exception_type) { + if (exception_type == v8::debug::kException && is_uncaught) { + gdb_server_->GetTarget().OnException( + isolate_, WasmModuleDebug::GetCallStack(id_, isolate_)); + gdb_server_->RunMessageLoopOnPause(); + } +} + +bool GdbServer::DebugDelegate::IsFunctionBlackboxed( + // Executed in the isolate thread. + Local<debug::Script> script, const debug::Location& start, + const debug::Location& end) { + return false; +} + } // namespace gdb_server } // namespace wasm } // namespace internal |