Upgrade V8 to 8.8.278.14

Bug: 162604069
Bug: 167389063
Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases
      --test com.google.android.gts.devicepolicy.DeviceOwnerTest#testProxyPacProxyTest
Test: m -j proxy_resolver_v8_unittest && adb sync && adb shell \
      /data/nativetest/proxy_resolver_v8_unittest/proxy_resolver_v8_unittest

Merged-In: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
Change-Id: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
(cherry picked from commit 9580a23bc5b8874a0979001d3595d027cbb68128)
diff --git a/src/debug/wasm/gdb-server/DIR_METADATA b/src/debug/wasm/gdb-server/DIR_METADATA
new file mode 100644
index 0000000..3b428d9
--- /dev/null
+++ b/src/debug/wasm/gdb-server/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+  component: "Blink>JavaScript>WebAssembly"
+}
\ No newline at end of file
diff --git a/src/debug/wasm/gdb-server/OWNERS b/src/debug/wasm/gdb-server/OWNERS
new file mode 100644
index 0000000..e2c94e8
--- /dev/null
+++ b/src/debug/wasm/gdb-server/OWNERS
@@ -0,0 +1 @@
+paolosev@microsoft.com
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.cc b/src/debug/wasm/gdb-server/gdb-remote-util.cc
new file mode 100644
index 0000000..b4880ed
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+using std::string;
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+// GDB expects lower case values.
+static const char kHexChars[] = "0123456789abcdef";
+
+void UInt8ToHex(uint8_t byte, char chars[2]) {
+  DCHECK(chars);
+  chars[0] = kHexChars[byte >> 4];
+  chars[1] = kHexChars[byte & 0xF];
+}
+
+bool HexToUInt8(const char chars[2], uint8_t* byte) {
+  uint8_t o1, o2;
+  if (NibbleToUInt8(chars[0], &o1) && NibbleToUInt8(chars[1], &o2)) {
+    *byte = (o1 << 4) + o2;
+    return true;
+  }
+
+  return false;
+}
+
+bool NibbleToUInt8(char ch, uint8_t* byte) {
+  DCHECK(byte);
+
+  // Check for nibble of a-f
+  if ((ch >= 'a') && (ch <= 'f')) {
+    *byte = (ch - 'a' + 10);
+    return true;
+  }
+
+  // Check for nibble of A-F
+  if ((ch >= 'A') && (ch <= 'F')) {
+    *byte = (ch - 'A' + 10);
+    return true;
+  }
+
+  // Check for nibble of 0-9
+  if ((ch >= '0') && (ch <= '9')) {
+    *byte = (ch - '0');
+    return true;
+  }
+
+  // Not a valid nibble representation
+  return false;
+}
+
+std::vector<std::string> StringSplit(const string& instr, const char* delim) {
+  std::vector<std::string> result;
+
+  const char* in = instr.data();
+  if (nullptr == in) return result;
+
+  // Check if we have nothing to do
+  if (nullptr == delim) {
+    result.push_back(string(in));
+    return result;
+  }
+
+  while (*in) {
+    // Toss all preceeding delimiters
+    while (*in && strchr(delim, *in)) in++;
+
+    // If we still have something to process
+    if (*in) {
+      const char* start = in;
+      size_t len = 0;
+      // Keep moving forward for all valid chars
+      while (*in && (strchr(delim, *in) == nullptr)) {
+        len++;
+        in++;
+      }
+
+      // Build this token and add it to the array.
+      result.push_back(string{start, len});
+    }
+  }
+  return result;
+}
+
+std::string Mem2Hex(const uint8_t* mem, size_t count) {
+  std::vector<char> result(count * 2 + 1);
+  for (size_t i = 0; i < count; i++) UInt8ToHex(*mem++, &result[i * 2]);
+  result[count * 2] = '\0';
+  return result.data();
+}
+
+std::string Mem2Hex(const std::string& str) {
+  return Mem2Hex(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.h b/src/debug/wasm/gdb-server/gdb-remote-util.h
new file mode 100644
index 0000000..88a2715
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.h
@@ -0,0 +1,72 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+
+#include <string>
+#include <vector>
+#include "src/flags/flags.h"
+#include "src/utils/utils.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+#define TRACE_GDB_REMOTE(...)                                            \
+  do {                                                                   \
+    if (FLAG_trace_wasm_gdb_remote) PrintF("[gdb-remote] " __VA_ARGS__); \
+  } while (false)
+
+// Convert from 0-255 to a pair of ASCII chars (0-9,a-f).
+void UInt8ToHex(uint8_t byte, char chars[2]);
+
+// Convert a pair of hex chars into a value 0-255 or return false if either
+// input character is not a valid nibble.
+bool HexToUInt8(const char chars[2], uint8_t* byte);
+
+// Convert from ASCII (0-9,a-f,A-F) to 4b unsigned or return false if the
+// input char is unexpected.
+bool NibbleToUInt8(char ch, uint8_t* byte);
+
+std::vector<std::string> V8_EXPORT_PRIVATE StringSplit(const std::string& instr,
+                                                       const char* delim);
+
+// Convert the memory pointed to by {mem} into a hex string in GDB-remote
+// format.
+std::string Mem2Hex(const uint8_t* mem, size_t count);
+std::string Mem2Hex(const std::string& str);
+
+// For LLDB debugging, an address in a Wasm module code space is represented
+// with 64 bits, where the first 32 bits identify the module id:
+// +--------------------+--------------------+
+// |     module_id      |       offset       |
+// +--------------------+--------------------+
+//  <----- 32 bit -----> <----- 32 bit ----->
+class wasm_addr_t {
+ public:
+  wasm_addr_t(uint32_t module_id, uint32_t offset)
+      : module_id_(module_id), offset_(offset) {}
+  explicit wasm_addr_t(uint64_t address)
+      : module_id_(address >> 32), offset_(address & 0xffffffff) {}
+
+  inline uint32_t ModuleId() const { return module_id_; }
+  inline uint32_t Offset() const { return offset_; }
+
+  inline operator uint64_t() const {
+    return static_cast<uint64_t>(module_id_) << 32 | offset_;
+  }
+
+ private:
+  uint32_t module_id_;
+  uint32_t offset_;
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.cc b/src/debug/wasm/gdb-server/gdb-server-thread.cc
new file mode 100644
index 0000000..03b9b8f
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.cc
@@ -0,0 +1,118 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/session.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+GdbServerThread::GdbServerThread(GdbServer* gdb_server)
+    : Thread(v8::base::Thread::Options("GdbServerThread")),
+      gdb_server_(gdb_server),
+      start_semaphore_(0) {}
+
+bool GdbServerThread::StartAndInitialize() {
+  // Executed in the Isolate thread.
+  if (!Start()) {
+    return false;
+  }
+
+  // We need to make sure that {Stop} is never called before the thread has
+  // completely initialized {transport_} and {target_}. Otherwise there could be
+  // a race condition where in the main thread {Stop} might get called before
+  // the transport is created, and then in the GDBServer thread we may have time
+  // to setup the transport and block on accept() before the main thread blocks
+  // on joining the thread.
+  // The small performance hit caused by this Wait should be negligeable because
+  // this operation happensat most once per process and only when the
+  // --wasm-gdb-remote flag is set.
+  start_semaphore_.Wait();
+  return !!target_;
+}
+
+void GdbServerThread::CleanupThread() {
+  // Executed in the GdbServer thread.
+  v8::base::MutexGuard guard(&mutex_);
+
+  target_ = nullptr;
+  transport_ = nullptr;
+
+#if _WIN32
+  ::WSACleanup();
+#endif
+}
+
+void GdbServerThread::Run() {
+  // Executed in the GdbServer thread.
+#ifdef _WIN32
+  // Initialize Winsock
+  WSADATA wsaData;
+  int iResult = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
+  if (iResult != 0) {
+    TRACE_GDB_REMOTE("GdbServerThread::Run: WSAStartup failed\n");
+    return;
+  }
+#endif
+
+  // If the default port is not available, try any port.
+  SocketBinding socket_binding = SocketBinding::Bind(FLAG_wasm_gdb_remote_port);
+  if (!socket_binding.IsValid()) {
+    socket_binding = SocketBinding::Bind(0);
+  }
+  if (!socket_binding.IsValid()) {
+    TRACE_GDB_REMOTE("GdbServerThread::Run: Failed to bind any TCP port\n");
+    return;
+  }
+  TRACE_GDB_REMOTE("gdb-remote(%d) : Connect GDB with 'target remote :%d\n",
+                   __LINE__, socket_binding.GetBoundPort());
+
+  transport_ = socket_binding.CreateTransport();
+  target_ = std::make_unique<Target>(gdb_server_);
+
+  // Here we have completed the initialization, and the thread that called
+  // {StartAndInitialize} may resume execution.
+  start_semaphore_.Signal();
+
+  while (!target_->IsTerminated()) {
+    // Wait for incoming connections.
+    if (!transport_->AcceptConnection()) {
+      continue;
+    }
+
+    // Create a new session for this connection
+    Session session(transport_.get());
+    TRACE_GDB_REMOTE("GdbServerThread: Connected\n");
+
+    // Run this session for as long as it lasts
+    target_->Run(&session);
+  }
+  CleanupThread();
+}
+
+void GdbServerThread::Stop() {
+  // Executed in the Isolate thread.
+
+  // Synchronized, becauses {Stop} might be called while {Run} is still
+  // initializing {transport_} and {target_}. If this happens and the thread is
+  // blocked waiting for an incoming connection or GdbServer for incoming
+  // packets, it will unblocked when {transport_} is closed.
+  v8::base::MutexGuard guard(&mutex_);
+
+  if (target_) {
+    target_->Terminate();
+  }
+
+  if (transport_) {
+    transport_->Close();
+  }
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.h b/src/debug/wasm/gdb-server/gdb-server-thread.h
new file mode 100644
index 0000000..cca1e4a
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.h
@@ -0,0 +1,63 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+
+#include "src/base/platform/platform.h"
+#include "src/base/platform/semaphore.h"
+#include "src/debug/wasm/gdb-server/target.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+
+// class GdbServerThread spawns a thread where all communication with a debugger
+// happens.
+class GdbServerThread : public v8::base::Thread {
+ public:
+  explicit GdbServerThread(GdbServer* gdb_server);
+
+  // base::Thread
+  void Run() override;
+
+  // Starts the GDB-server thread and waits Run() method is called on the new
+  // thread and the initialization completes.
+  bool StartAndInitialize();
+
+  // Stops the GDB-server thread when the V8 process shuts down; gracefully
+  // closes any active debugging session.
+  void Stop();
+
+  Target& GetTarget() { return *target_; }
+
+ private:
+  void CleanupThread();
+
+  GdbServer* gdb_server_;
+
+  // Used to block the caller on StartAndInitialize() waiting for the new thread
+  // to have completed its initialization.
+  // (Note that Thread::StartSynchronously() wouldn't work in this case because
+  // it returns as soon as the new thread starts, but before Run() is called).
+  base::Semaphore start_semaphore_;
+
+  base::Mutex mutex_;
+  // Protected by {mutex_}:
+  std::unique_ptr<TransportBase> transport_;
+  std::unique_ptr<Target> target_;
+
+  DISALLOW_COPY_AND_ASSIGN(GdbServerThread);
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server.cc b/src/debug/wasm/gdb-server/gdb-server.cc
new file mode 100644
index 0000000..96e2308
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.cc
@@ -0,0 +1,424 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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/utils/locked-queue-inl.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+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);
+
+  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");
+    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
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server.h b/src/debug/wasm/gdb-server/gdb-server.h
new file mode 100644
index 0000000..50939af
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.h
@@ -0,0 +1,201 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+
+#include <map>
+#include <memory>
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class TaskRunner;
+
+// class GdbServer acts as a manager for the GDB-remote stub. It is instantiated
+// as soon as the first Wasm module is loaded in the Wasm engine and spawns a
+// separate thread to accept connections and exchange messages with a debugger.
+// It will contain the logic to serve debugger queries and access the state of
+// the Wasm engine.
+class GdbServer {
+ public:
+  // Factory method: creates and returns a GdbServer. Spawns a "GDB-remote"
+  // thread that will be used to communicate with the debugger.
+  // May return null on failure.
+  // This should be called once, the first time a Wasm module is loaded in the
+  // Wasm engine.
+  static std::unique_ptr<GdbServer> Create();
+
+  // Stops the "GDB-remote" thread and waits for it to complete. This should be
+  // called once, when the Wasm engine shuts down.
+  ~GdbServer();
+
+  // Queries the set of the Wasm modules currently loaded. Each module is
+  // identified by a unique integer module id.
+  struct WasmModuleInfo {
+    uint32_t module_id;
+    std::string module_name;
+  };
+  std::vector<WasmModuleInfo> GetLoadedModules();
+
+  // Queries the value of the {index} global value in the Wasm module identified
+  // by {frame_index}.
+  //
+  bool GetWasmGlobal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+                     uint32_t buffer_size, uint32_t* size);
+
+  // Queries the value of the {index} local value in the {frame_index}th stack
+  // frame in the Wasm module identified by {frame_index}.
+  //
+  bool GetWasmLocal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+                    uint32_t buffer_size, uint32_t* size);
+
+  // Queries the value of the {index} value in the operand stack.
+  //
+  bool GetWasmStackValue(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+                         uint32_t buffer_size, uint32_t* size);
+
+  // Reads {size} bytes, starting from {offset}, from the Memory instance
+  // associated to the Wasm module identified by {frame_index}.
+  // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+  // Note: only one Memory for Module is currently supported.
+  //
+  uint32_t GetWasmMemory(uint32_t frame_index, uint32_t offset, uint8_t* buffer,
+                         uint32_t size);
+
+  // Reads {size} bytes, starting from the low dword of {address}, from the Code
+  // space of th Wasm module identified by high dword of {address}.
+  // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+  uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
+                              uint32_t size);
+
+  // Inserts a breakpoint at the offset {offset} of the Wasm module identified
+  // by {wasm_module_id}.
+  // Returns true if the breakpoint was successfully added.
+  bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+  // Removes a breakpoint at the offset {offset} of the Wasm module identified
+  // by {wasm_module_id}.
+  // Returns true if the breakpoint was successfully removed.
+  bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+  // Returns the current call stack as a vector of program counters.
+  std::vector<wasm_addr_t> GetWasmCallStack() const;
+
+  // Manage the set of Isolates for this GdbServer.
+  void AddIsolate(Isolate* isolate);
+  void RemoveIsolate(Isolate* isolate);
+
+  // Requests that the thread suspend execution at the next Wasm instruction.
+  void Suspend();
+
+  // Handle stepping in wasm functions via the wasm interpreter.
+  void PrepareStep();
+
+  // Called when the target debuggee can resume execution (for example after
+  // having been suspended on a breakpoint). Terminates the task runner leaving
+  // all pending tasks in the queue.
+  void QuitMessageLoopOnPause();
+
+ private:
+  GdbServer();
+
+  // When the target debuggee is suspended for a breakpoint or exception, blocks
+  // the main (isolate) thread and enters in a message loop. Here it waits on a
+  // queue of Task objects that are posted by the GDB-stub thread and that
+  // represent queries received from the debugger via the GDB-remote protocol.
+  void RunMessageLoopOnPause();
+
+  // Post a task to run a callback in the isolate thread.
+  template <typename Callback>
+  auto RunSyncTask(Callback&& callback) const;
+
+  void AddWasmModule(uint32_t module_id, Local<debug::WasmScript> wasm_script);
+
+  // Given a Wasm module id, retrieves the corresponding debugging WasmScript
+  // object.
+  bool GetModuleDebugHandler(uint32_t module_id,
+                             WasmModuleDebug** wasm_module_debug);
+
+  // Returns the debugging target.
+  Target& GetTarget() const;
+
+  // Class DebugDelegate implements the debug::DebugDelegate interface to
+  // receive notifications when debug events happen in a given isolate, like a
+  // script being loaded, a breakpoint being hit, an exception being thrown.
+  class DebugDelegate : public debug::DebugDelegate {
+   public:
+    DebugDelegate(Isolate* isolate, GdbServer* gdb_server);
+    ~DebugDelegate();
+
+    // debug::DebugDelegate
+    void ScriptCompiled(Local<debug::Script> script, bool is_live_edited,
+                        bool has_compile_error) override;
+    void BreakProgramRequested(Local<v8::Context> paused_context,
+                               const std::vector<debug::BreakpointId>&
+                                   inspector_break_points_hit) override;
+    void ExceptionThrown(Local<v8::Context> paused_context,
+                         Local<Value> exception, Local<Value> promise,
+                         bool is_uncaught,
+                         debug::ExceptionType exception_type) override;
+    bool IsFunctionBlackboxed(Local<debug::Script> script,
+                              const debug::Location& start,
+                              const debug::Location& end) override;
+
+   private:
+    // Calculates module_id as:
+    // +--------------------+------------------- +
+    // | DebugDelegate::id_ |    Script::Id()    |
+    // +--------------------+------------------- +
+    //  <----- 16 bit -----> <----- 16 bit ----->
+    uint32_t GetModuleId(uint32_t script_id) const {
+      DCHECK_LT(script_id, 0x10000);
+      DCHECK_LT(id_, 0x10000);
+      return id_ << 16 | script_id;
+    }
+
+    Isolate* isolate_;
+    uint32_t id_;
+    GdbServer* gdb_server_;
+
+    static std::atomic<uint32_t> id_s;
+  };
+
+  // The GDB-stub thread where all the communication with the debugger happens.
+  std::unique_ptr<GdbServerThread> thread_;
+
+  // Used to transform the queries that arrive in the GDB-stub thread into
+  // tasks executed in the main (isolate) thread.
+  std::unique_ptr<TaskRunner> task_runner_;
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Always accessed in the isolate thread.
+
+  // Set of breakpoints currently defines in Wasm code.
+  typedef std::map<uint64_t, int> BreakpointsMap;
+  BreakpointsMap breakpoints_;
+
+  typedef std::map<uint32_t, WasmModuleDebug> ScriptsMap;
+  ScriptsMap scripts_;
+
+  typedef std::map<Isolate*, std::unique_ptr<DebugDelegate>>
+      IsolateDebugDelegateMap;
+  IsolateDebugDelegateMap isolate_delegates_;
+
+  // End of fields always accessed in the isolate thread.
+  //////////////////////////////////////////////////////////////////////////////
+
+  DISALLOW_COPY_AND_ASSIGN(GdbServer);
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
diff --git a/src/debug/wasm/gdb-server/packet.cc b/src/debug/wasm/gdb-server/packet.cc
new file mode 100644
index 0000000..f8306c4
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.cc
@@ -0,0 +1,364 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Packet::Packet() {
+  seq_ = -1;
+  Clear();
+}
+
+void Packet::Clear() {
+  data_.clear();
+  read_index_ = 0;
+}
+
+void Packet::Rewind() { read_index_ = 0; }
+
+bool Packet::EndOfPacket() const { return (read_index_ >= GetPayloadSize()); }
+
+void Packet::AddRawChar(char ch) { data_.push_back(ch); }
+
+void Packet::AddWord8(uint8_t byte) {
+  char seq[2];
+  UInt8ToHex(byte, seq);
+  AddRawChar(seq[0]);
+  AddRawChar(seq[1]);
+}
+
+void Packet::AddBlock(const void* ptr, uint32_t len) {
+  DCHECK(ptr);
+
+  const char* p = (const char*)ptr;
+
+  for (uint32_t offs = 0; offs < len; offs++) {
+    AddWord8(p[offs]);
+  }
+}
+
+void Packet::AddString(const char* str) {
+  DCHECK(str);
+
+  while (*str) {
+    AddRawChar(*str);
+    str++;
+  }
+}
+
+void Packet::AddHexString(const char* str) {
+  DCHECK(str);
+
+  while (*str) {
+    AddWord8(*str);
+    str++;
+  }
+}
+
+void Packet::AddNumberSep(uint64_t val, char sep) {
+  char out[sizeof(val) * 2];
+  char temp[2];
+
+  // Check for -1 optimization
+  if (val == static_cast<uint64_t>(-1)) {
+    AddRawChar('-');
+    AddRawChar('1');
+  } else {
+    int nibbles = 0;
+
+    // In the GDB remote protocol numbers are formatted as big-endian hex
+    // strings. Leading zeros can be skipped.
+    // For example the value 0x00001234 is formatted as "1234".
+    for (size_t a = 0; a < sizeof(val); a++) {
+      uint8_t byte = static_cast<uint8_t>(val & 0xFF);
+
+      // Stream in with bytes reversed, starting with the least significant.
+      // So if we have the value 0x00001234, we store 4, then 3, 2, 1.
+      // Note that the characters are later reversed to be in big-endian order.
+      UInt8ToHex(byte, temp);
+      out[nibbles++] = temp[1];
+      out[nibbles++] = temp[0];
+
+      // Get the next 8 bits;
+      val >>= 8;
+
+      // Suppress leading zeros, so we are done when val hits zero
+      if (val == 0) {
+        break;
+      }
+    }
+
+    // Strip the high zero for this byte if present.
+    if ((nibbles > 1) && (out[nibbles - 1] == '0')) nibbles--;
+
+    // Now write it out reverse to correct the order
+    while (nibbles) {
+      nibbles--;
+      AddRawChar(out[nibbles]);
+    }
+  }
+
+  // If we asked for a separator, insert it
+  if (sep) AddRawChar(sep);
+}
+
+bool Packet::GetNumberSep(uint64_t* val, char* sep) {
+  uint64_t out = 0;
+  char ch;
+  if (!GetRawChar(&ch)) {
+    return false;
+  }
+
+  // Numbers are formatted as a big-endian hex strings.
+  // The literals "0" and "-1" as special cases.
+
+  // Check for -1
+  if (ch == '-') {
+    if (!GetRawChar(&ch)) {
+      return false;
+    }
+
+    if (ch == '1') {
+      *val = (uint64_t)-1;
+
+      ch = 0;
+      GetRawChar(&ch);
+      if (sep) {
+        *sep = ch;
+      }
+      return true;
+    }
+    return false;
+  }
+
+  do {
+    uint8_t nib;
+
+    // Check for separator
+    if (!NibbleToUInt8(ch, &nib)) {
+      break;
+    }
+
+    // Add this nibble.
+    out = (out << 4) + nib;
+
+    // Get the next character (if availible)
+    ch = 0;
+    if (!GetRawChar(&ch)) {
+      break;
+    }
+  } while (1);
+
+  // Set the value;
+  *val = out;
+
+  // Add the separator if the user wants it...
+  if (sep != nullptr) *sep = ch;
+
+  return true;
+}
+
+bool Packet::GetRawChar(char* ch) {
+  DCHECK(ch != nullptr);
+
+  if (read_index_ >= GetPayloadSize()) return false;
+
+  *ch = data_[read_index_++];
+
+  // Check for RLE X*N, where X is the value, N is the reps.
+  if (*ch == '*') {
+    if (read_index_ < 2) {
+      TRACE_GDB_REMOTE("Unexpected RLE at start of packet.\n");
+      return false;
+    }
+
+    if (read_index_ >= GetPayloadSize()) {
+      TRACE_GDB_REMOTE("Unexpected EoP during RLE.\n");
+      return false;
+    }
+
+    // GDB does not use "CTRL" characters in the stream, so the
+    // number of reps is encoded as the ASCII value beyond 28
+    // (which when you add a min rep size of 4, forces the rep
+    // character to be ' ' (32) or greater).
+    int32_t cnt = (data_[read_index_] - 28);
+    if (cnt < 3) {
+      TRACE_GDB_REMOTE("Unexpected RLE length.\n");
+      return false;
+    }
+
+    // We have just read '*' and incremented the read pointer,
+    // so here is the old state, and expected new state.
+    //
+    //   Assume N = 5, we grow by N - size of encoding (3).
+    //
+    // OldP:       R  W
+    // OldD:  012X*N89 = 8 chars
+    // Size:  012X*N89__ = 10 chars
+    // Move:  012X*__N89 = 10 chars
+    // Fill:  012XXXXX89 = 10 chars
+    // NewP:       R    W  (shifted 5 - 3)
+
+    // First, store the remaining characters to the right into a temp string.
+    std::string right = data_.substr(read_index_ + 1);
+    // Discard the '*' we just read
+    data_.erase(read_index_ - 1);
+    // Append (N-1) 'X' chars
+    *ch = data_[read_index_ - 2];
+    data_.append(cnt - 1, *ch);
+    // Finally, append the remaining characters
+    data_.append(right);
+  }
+  return true;
+}
+
+bool Packet::GetWord8(uint8_t* value) {
+  DCHECK(value);
+
+  // Get two ASCII hex values and convert them to ints
+  char seq[2];
+  if (!GetRawChar(&seq[0]) || !GetRawChar(&seq[1])) {
+    return false;
+  }
+  return HexToUInt8(seq, value);
+}
+
+bool Packet::GetBlock(void* ptr, uint32_t len) {
+  DCHECK(ptr);
+
+  uint8_t* p = reinterpret_cast<uint8_t*>(ptr);
+  bool res = true;
+
+  for (uint32_t offs = 0; offs < len; offs++) {
+    res = GetWord8(&p[offs]);
+    if (false == res) {
+      break;
+    }
+  }
+
+  return res;
+}
+
+bool Packet::GetString(std::string* str) {
+  if (EndOfPacket()) {
+    return false;
+  }
+
+  *str = data_.substr(read_index_);
+  read_index_ = GetPayloadSize();
+  return true;
+}
+
+bool Packet::GetHexString(std::string* str) {
+  // Decode a string encoded as a series of 2-hex digit pairs.
+
+  if (EndOfPacket()) {
+    return false;
+  }
+
+  // Pull values until we hit a separator
+  str->clear();
+  char ch1;
+  while (GetRawChar(&ch1)) {
+    uint8_t nib1;
+    if (!NibbleToUInt8(ch1, &nib1)) {
+      read_index_--;
+      break;
+    }
+    char ch2;
+    uint8_t nib2;
+    if (!GetRawChar(&ch2) || !NibbleToUInt8(ch2, &nib2)) {
+      return false;
+    }
+    *str += static_cast<char>((nib1 << 4) + nib2);
+  }
+  return true;
+}
+
+const char* Packet::GetPayload() const { return data_.c_str(); }
+
+size_t Packet::GetPayloadSize() const { return data_.size(); }
+
+bool Packet::GetSequence(int32_t* ch) const {
+  DCHECK(ch);
+
+  if (seq_ != -1) {
+    *ch = seq_;
+    return true;
+  }
+
+  return false;
+}
+
+void Packet::ParseSequence() {
+  size_t saved_read_index = read_index_;
+  unsigned char seq;
+  char ch;
+  if (GetWord8(&seq) && GetRawChar(&ch)) {
+    if (ch == ':') {
+      SetSequence(seq);
+      return;
+    }
+  }
+  // No sequence number present, so reset to original position.
+  read_index_ = saved_read_index;
+}
+
+void Packet::SetSequence(int32_t val) { seq_ = val; }
+
+void Packet::SetError(ErrDef error) {
+  Clear();
+  AddRawChar('E');
+  AddWord8(static_cast<uint8_t>(error));
+}
+
+std::string Packet::GetPacketData() const {
+  char chars[2];
+  const char* ptr = GetPayload();
+  size_t size = GetPayloadSize();
+
+  std::stringstream outstr;
+
+  // Signal start of response
+  outstr << '$';
+
+  char run_xsum = 0;
+
+  // If there is a sequence, send as two nibble 8bit value + ':'
+  int32_t seq;
+  if (GetSequence(&seq)) {
+    UInt8ToHex(seq, chars);
+    outstr << chars[0];
+    run_xsum += chars[0];
+    outstr << chars[1];
+    run_xsum += chars[1];
+
+    outstr << ':';
+    run_xsum += ':';
+  }
+
+  // Send the main payload
+  for (size_t offs = 0; offs < size; ++offs) {
+    outstr << ptr[offs];
+    run_xsum += ptr[offs];
+  }
+
+  // Send XSUM as two nibble 8bit value preceeded by '#'
+  outstr << '#';
+  UInt8ToHex(run_xsum, chars);
+  outstr << chars[0];
+  outstr << chars[1];
+
+  return outstr.str();
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/packet.h b/src/debug/wasm/gdb-server/packet.h
new file mode 100644
index 0000000..4308081
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.h
@@ -0,0 +1,105 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+
+#include <string>
+#include <vector>
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class V8_EXPORT_PRIVATE Packet {
+ public:
+  Packet();
+
+  // Empty the vector and reset the read/write pointers.
+  void Clear();
+
+  // Reset the read pointer, allowing the packet to be re-read.
+  void Rewind();
+
+  // Return true of the read pointer has reached the write pointer.
+  bool EndOfPacket() const;
+
+  // Store a single raw 8 bit value
+  void AddRawChar(char ch);
+
+  // Store a block of data as hex pairs per byte
+  void AddBlock(const void* ptr, uint32_t len);
+
+  // Store a byte as a 2 chars block.
+  void AddWord8(uint8_t val);
+
+  // Store a number up to 64 bits, formatted as a big-endian hex string with
+  // preceeding zeros removed.  Since zeros can be removed, the width of this
+  // number is unknown, and the number is always followed by a NULL or a
+  // separator (non hex digit).
+  void AddNumberSep(uint64_t val, char sep);
+
+  // Add a raw string.
+  void AddString(const char* str);
+
+  // Add a string stored as a stream of ASCII hex digit pairs.  It is safe
+  // to use any non-null character in this stream.  If this does not terminate
+  // the packet, there should be a separator (non hex digit) immediately
+  // following.
+  void AddHexString(const char* str);
+
+  // Retrieve a single character if available
+  bool GetRawChar(char* ch);
+
+  // Retrieve "len" ASCII character pairs.
+  bool GetBlock(void* ptr, uint32_t len);
+
+  // Retrieve a 8, 16, 32, or 64 bit word as pairs of hex digits.  These
+  // functions will always consume bits/4 characters from the stream.
+  bool GetWord8(uint8_t* val);
+
+  // Retrieve a number (formatted as a big-endian hex string) and a separator.
+  // If 'sep' is null, the separator is consumed but thrown away.
+  bool GetNumberSep(uint64_t* val, char* sep);
+
+  // Get a string from the stream
+  bool GetString(std::string* str);
+  bool GetHexString(std::string* str);
+
+  // Return a pointer to the entire packet payload
+  const char* GetPayload() const;
+  size_t GetPayloadSize() const;
+
+  // Returns true and the sequence number, or false if it is unset.
+  bool GetSequence(int32_t* seq) const;
+
+  // Parses sequence number in package data and moves read pointer past it.
+  void ParseSequence();
+
+  // Set the sequence number.
+  void SetSequence(int32_t seq);
+
+  enum class ErrDef { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+  void SetError(ErrDef);
+
+  // Returns the full content of a GDB-remote packet, in the format:
+  //    $payload#checksum
+  // where the two-digit checksum is computed as the modulo 256 sum of all
+  // characters between the leading ‘$’ and the trailing ‘#’.
+  std::string GetPacketData() const;
+
+ private:
+  int32_t seq_;
+  std::string data_;
+  size_t read_index_;
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
diff --git a/src/debug/wasm/gdb-server/session.cc b/src/debug/wasm/gdb-server/session.cc
new file mode 100644
index 0000000..b052934
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.cc
@@ -0,0 +1,148 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Session::Session(TransportBase* transport)
+    : io_(transport), connected_(true), ack_enabled_(true) {}
+
+void Session::WaitForDebugStubEvent() { io_->WaitForDebugStubEvent(); }
+
+bool Session::SignalThreadEvent() { return io_->SignalThreadEvent(); }
+
+bool Session::IsDataAvailable() const { return io_->IsDataAvailable(); }
+
+bool Session::IsConnected() const { return connected_; }
+
+void Session::Disconnect() {
+  io_->Disconnect();
+  connected_ = false;
+}
+
+bool Session::GetChar(char* ch) {
+  if (!io_->Read(ch, 1)) {
+    Disconnect();
+    return false;
+  }
+
+  return true;
+}
+
+bool Session::SendPacket(Packet* pkt, bool expect_ack) {
+  char ch;
+  do {
+    std::string data = pkt->GetPacketData();
+
+    TRACE_GDB_REMOTE("TX %s\n", data.size() < 160
+                                    ? data.c_str()
+                                    : (data.substr(0, 160) + "...").c_str());
+    if (!io_->Write(data.data(), static_cast<int32_t>(data.length()))) {
+      return false;
+    }
+
+    // If ACKs are off, we are done.
+    if (!expect_ack || !ack_enabled_) {
+      break;
+    }
+
+    // Otherwise, poll for '+'
+    if (!GetChar(&ch)) {
+      return false;
+    }
+
+    // Retry if we didn't get a '+'
+  } while (ch != '+');
+
+  return true;
+}
+
+bool Session::GetPayload(Packet* pkt, uint8_t* checksum) {
+  pkt->Clear();
+  *checksum = 0;
+
+  // Stream in the characters
+  char ch;
+  while (GetChar(&ch)) {
+    if (ch == '#') {
+      // If we see a '#' we must be done with the data.
+      return true;
+    } else if (ch == '$') {
+      // If we see a '$' we must have missed the last cmd, let's retry.
+      TRACE_GDB_REMOTE("RX Missing $, retry.\n");
+      *checksum = 0;
+      pkt->Clear();
+    } else {
+      // Keep a running XSUM.
+      *checksum += ch;
+      pkt->AddRawChar(ch);
+    }
+  }
+  return false;
+}
+
+bool Session::GetPacket(Packet* pkt) {
+  while (true) {
+    // Toss characters until we see a start of command
+    char ch;
+    do {
+      if (!GetChar(&ch)) {
+        return false;
+      }
+    } while (ch != '$');
+
+    uint8_t running_checksum = 0;
+    if (!GetPayload(pkt, &running_checksum)) {
+      return false;
+    }
+
+    // Get two nibble checksum
+    uint8_t trailing_checksum = 0;
+    char chars[2];
+    if (!GetChar(&chars[0]) || !GetChar(&chars[1]) ||
+        !HexToUInt8(chars, &trailing_checksum)) {
+      return false;
+    }
+
+    TRACE_GDB_REMOTE("RX $%s#%c%c\n", pkt->GetPayload(), chars[0], chars[1]);
+
+    pkt->ParseSequence();
+
+    // If ACKs are off, we are done.
+    if (!ack_enabled_) {
+      return true;
+    }
+
+    // If the XSUMs don't match, signal bad packet
+    if (trailing_checksum == running_checksum) {
+      char out[3] = {'+', 0, 0};
+
+      // If we have a sequence number
+      int32_t seq;
+      if (pkt->GetSequence(&seq)) {
+        // Respond with sequence number
+        UInt8ToHex(seq, &out[1]);
+        return io_->Write(out, 3);
+      } else {
+        return io_->Write(out, 1);
+      }
+    } else {
+      // Resend a bad XSUM and look for retransmit
+      TRACE_GDB_REMOTE("RX Bad XSUM, retry\n");
+      io_->Write("-", 1);
+      // retry...
+    }
+  }
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/session.h b/src/debug/wasm/gdb-server/session.h
new file mode 100644
index 0000000..d7c2263
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.h
@@ -0,0 +1,73 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+#define V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class Packet;
+class TransportBase;
+
+// Represents a gdb-remote debugging session.
+class V8_EXPORT_PRIVATE Session {
+ public:
+  explicit Session(TransportBase* transport);
+
+  // Attempt to send a packet and optionally wait for an ACK from the receiver.
+  bool SendPacket(Packet* packet, bool expect_ack = true);
+
+  // Attempt to receive a packet.
+  bool GetPacket(Packet* packet);
+
+  // Return true if there is data to read.
+  bool IsDataAvailable() const;
+
+  // Return true if the connection is still valid.
+  bool IsConnected() const;
+
+  // Shutdown the connection.
+  void Disconnect();
+
+  // When a debugging session is active, the GDB-remote thread can block waiting
+  // for events and it will resume execution when one of these two events arise:
+  // - A network event (a new packet arrives, or the connection is dropped)
+  // - A thread event (the execution stopped because of a trap or breakpoint).
+  void WaitForDebugStubEvent();
+
+  // Signal that the debuggee execution stopped because of a trap or breakpoint.
+  bool SignalThreadEvent();
+
+  // By default, when either the debugger or the GDB-stub sends a packet,
+  // the first response expected is an acknowledgment: either '+' (to indicate
+  // the packet was received correctly) or '-' (to request retransmission).
+  // When a transport is reliable, the debugger may request that acknowledgement
+  // be disabled by means of the 'QStartNoAckMode' packet.
+  void EnableAck(bool ack_enabled) { ack_enabled_ = ack_enabled; }
+
+ private:
+  // Read a single character from the transport.
+  bool GetChar(char* ch);
+
+  // Read the content of a packet, from a leading '$' to a trailing '#'.
+  bool GetPayload(Packet* pkt, uint8_t* checksum);
+
+  TransportBase* io_;  // Transport object not owned by the Session.
+  bool connected_;     // Is the connection still valid.
+  bool ack_enabled_;   // If true, emit or wait for '+' from RSP stream.
+
+  DISALLOW_COPY_AND_ASSIGN(Session);
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
diff --git a/src/debug/wasm/gdb-server/target.cc b/src/debug/wasm/gdb-server/target.cc
new file mode 100644
index 0000000..6992fd1
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.cc
@@ -0,0 +1,679 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/target.h"
+
+#include <inttypes.h>
+#include "src/base/platform/time.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+static const int kThreadId = 1;
+
+// Signals.
+static const int kSigTrace = 5;
+static const int kSigSegv = 11;
+
+Target::Target(GdbServer* gdb_server)
+    : gdb_server_(gdb_server),
+      status_(Status::Running),
+      cur_signal_(0),
+      session_(nullptr),
+      debugger_initial_suspension_(true),
+      semaphore_(0),
+      current_isolate_(nullptr) {
+  InitQueryPropertyMap();
+}
+
+void Target::InitQueryPropertyMap() {
+  // Request LLDB to send packets up to 4000 bytes for bulk transfers.
+  query_properties_["Supported"] =
+      "PacketSize=1000;vContSupported-;qXfer:libraries:read+;";
+
+  query_properties_["Attached"] = "1";
+
+  // There is only one register, named 'pc', in this architecture
+  query_properties_["RegisterInfo0"] =
+      "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:"
+      "General Purpose Registers;gcc:16;dwarf:16;generic:pc;";
+  query_properties_["RegisterInfo1"] = "E45";
+
+  // ProcessInfo for wasm32
+  query_properties_["ProcessInfo"] =
+      "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" +
+      Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;";
+  query_properties_["Symbol"] = "OK";
+
+  // Current thread info
+  char buff[16];
+  snprintf(buff, sizeof(buff), "QC%x", kThreadId);
+  query_properties_["C"] = buff;
+}
+
+void Target::Terminate() {
+  // Executed in the Isolate thread, when the process shuts down.
+  SetStatus(Status::Terminated);
+}
+
+void Target::OnProgramBreak(Isolate* isolate,
+                            const std::vector<wasm_addr_t>& call_frames) {
+  OnSuspended(isolate, kSigTrace, call_frames);
+}
+void Target::OnException(Isolate* isolate,
+                         const std::vector<wasm_addr_t>& call_frames) {
+  OnSuspended(isolate, kSigSegv, call_frames);
+}
+void Target::OnSuspended(Isolate* isolate, int signal,
+                         const std::vector<wasm_addr_t>& call_frames) {
+  // This function will be called in the isolate thread, when the wasm
+  // interpreter gets suspended.
+
+  bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension);
+  SetStatus(Status::Suspended, signal, call_frames, isolate);
+  if (isWaitingForSuspension) {
+    // Wake the GdbServer thread that was blocked waiting for the Target
+    // to suspend.
+    semaphore_.Signal();
+  } else if (session_) {
+    session_->SignalThreadEvent();
+  }
+}
+
+void Target::Run(Session* session) {
+  // Executed in the GdbServer thread.
+  session_ = session;
+  do {
+    WaitForDebugEvent();
+    ProcessDebugEvent();
+    ProcessCommands();
+  } while (!IsTerminated() && session_->IsConnected());
+  session_ = nullptr;
+}
+
+void Target::WaitForDebugEvent() {
+  // Executed in the GdbServer thread.
+
+  if (status_ == Status::Running) {
+    // Wait for either:
+    //   * the thread to fault (or single-step)
+    //   * an interrupt from LLDB
+    session_->WaitForDebugStubEvent();
+  }
+}
+
+void Target::ProcessDebugEvent() {
+  // Executed in the GdbServer thread
+
+  if (status_ == Status::Running) {
+    // Blocks, waiting for the engine to suspend.
+    Suspend();
+  }
+
+  // Here, the wasm interpreter has suspended and we have updated the current
+  // thread info.
+
+  if (debugger_initial_suspension_) {
+    // First time on a connection, we don't send the signal.
+    // All other times, send the signal that triggered us.
+    debugger_initial_suspension_ = false;
+  } else {
+    Packet pktOut;
+    SetStopReply(&pktOut);
+    session_->SendPacket(&pktOut, false);
+  }
+}
+
+void Target::Suspend() {
+  // Executed in the GdbServer thread
+  if (status_ == Status::Running) {
+    // TODO(paolosev) - this only suspends the wasm interpreter.
+    gdb_server_->Suspend();
+
+    status_ = Status::WaitingForSuspension;
+  }
+
+  while (status_ == Status::WaitingForSuspension) {
+    if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) {
+      // Here the wasm interpreter is suspended.
+      return;
+    }
+  }
+}
+
+void Target::ProcessCommands() {
+  // GDB-remote messages are processed in the GDBServer thread.
+
+  if (IsTerminated()) {
+    return;
+  } else if (status_ != Status::Suspended) {
+    // Don't process commands if we haven't stopped.
+    return;
+  }
+
+  // Now we are ready to process commands.
+  // Loop through packets until we process a continue packet or a detach.
+  Packet recv, reply;
+  while (session_->IsConnected()) {
+    if (!session_->GetPacket(&recv)) {
+      continue;
+    }
+
+    reply.Clear();
+    ProcessPacketResult result = ProcessPacket(&recv, &reply);
+    switch (result) {
+      case ProcessPacketResult::Paused:
+        session_->SendPacket(&reply);
+        break;
+
+      case ProcessPacketResult::Continue:
+        DCHECK_EQ(status_, Status::Running);
+        // If this is a continue type command, break out of this loop.
+        gdb_server_->QuitMessageLoopOnPause();
+        return;
+
+      case ProcessPacketResult::Detach:
+        SetStatus(Status::Running);
+        session_->SendPacket(&reply);
+        session_->Disconnect();
+        gdb_server_->QuitMessageLoopOnPause();
+        return;
+
+      case ProcessPacketResult::Kill:
+        session_->SendPacket(&reply);
+        exit(-9);
+
+      default:
+        UNREACHABLE();
+    }
+  }
+
+  if (!session_->IsConnected()) {
+    debugger_initial_suspension_ = true;
+  }
+}
+
+Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
+                                                  Packet* pkt_out) {
+  ErrorCode err = ErrorCode::None;
+
+  // Clear the outbound message.
+  pkt_out->Clear();
+
+  // Set the sequence number, if present.
+  int32_t seq = -1;
+  if (pkt_in->GetSequence(&seq)) {
+    pkt_out->SetSequence(seq);
+  }
+
+  // A GDB-remote packet begins with an upper- or lower-case letter, which
+  // generally represents a single command.
+  // The letters 'q' and 'Q' introduce a "General query packets" and are used
+  // to extend the protocol with custom commands.
+  // The format of GDB-remote commands is documented here:
+  // https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview.
+  char cmd;
+  pkt_in->GetRawChar(&cmd);
+
+  switch (cmd) {
+    // Queries the reason the target halted.
+    // IN : $?
+    // OUT: A Stop-reply packet
+    case '?':
+      SetStopReply(pkt_out);
+      break;
+
+    // Resumes execution
+    // IN : $c
+    // OUT: A Stop-reply packet is sent later, when the execution halts.
+    case 'c':
+      SetStatus(Status::Running);
+      return ProcessPacketResult::Continue;
+
+    // Detaches the debugger from this target
+    // IN : $D
+    // OUT: $OK
+    case 'D':
+      TRACE_GDB_REMOTE("Requested Detach.\n");
+      pkt_out->AddString("OK");
+      return ProcessPacketResult::Detach;
+
+    // Read general registers (We only support register 'pc' that contains
+    // the current instruction pointer).
+    // IN : $g
+    // OUT: $xx...xx
+    case 'g': {
+      uint64_t pc = GetCurrentPc();
+      pkt_out->AddBlock(&pc, sizeof(pc));
+      break;
+    }
+
+    // Write general registers - NOT SUPPORTED
+    // IN : $Gxx..xx
+    // OUT: $ (empty string)
+    case 'G': {
+      break;
+    }
+
+    // Set thread for subsequent operations. For Wasm targets, we currently
+    // assume that there is only one thread with id = kThreadId (= 1).
+    // IN : $H(c/g)(-1,0,xxxx)
+    // OUT: $OK
+    case 'H': {
+      // Type of the operation (‘m’, ‘M’, ‘g’, ‘G’, ...)
+      char operation;
+      if (!pkt_in->GetRawChar(&operation)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+
+      uint64_t thread_id;
+      if (!pkt_in->GetNumberSep(&thread_id, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+
+      // Ignore, only one thread supported for now.
+      pkt_out->AddString("OK");
+      break;
+    }
+
+    // Kills the debuggee.
+    // IN : $k
+    // OUT: $OK
+    case 'k':
+      TRACE_GDB_REMOTE("Requested Kill.\n");
+      pkt_out->AddString("OK");
+      return ProcessPacketResult::Kill;
+
+    // Reads {llll} addressable memory units starting at address {aaaa}.
+    // IN : $maaaa,llll
+    // OUT: $xx..xx
+    case 'm': {
+      uint64_t address;
+      if (!pkt_in->GetNumberSep(&address, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+      wasm_addr_t wasm_addr(address);
+
+      uint64_t len;
+      if (!pkt_in->GetNumberSep(&len, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+
+      if (len > Transport::kBufSize / 2) {
+        err = ErrorCode::BadArgs;
+        break;
+      }
+
+      uint32_t length = static_cast<uint32_t>(len);
+      uint8_t buff[Transport::kBufSize];
+      if (wasm_addr.ModuleId() > 0) {
+        uint32_t read =
+            gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length);
+        if (read > 0) {
+          pkt_out->AddBlock(buff, read);
+        } else {
+          err = ErrorCode::Failed;
+        }
+      } else {
+        err = ErrorCode::BadArgs;
+      }
+      break;
+    }
+
+    // Writes {llll} addressable memory units starting at address {aaaa}.
+    // IN : $Maaaa,llll:xx..xx
+    // OUT: $OK
+    case 'M': {
+      // Writing to memory not supported for Wasm.
+      err = ErrorCode::Failed;
+      break;
+    }
+
+    // pN: Reads the value of register N.
+    // IN : $pxx
+    // OUT: $xx..xx
+    case 'p': {
+      uint64_t pc = GetCurrentPc();
+      pkt_out->AddBlock(&pc, sizeof(pc));
+    } break;
+
+    case 'q': {
+      err = ProcessQueryPacket(pkt_in, pkt_out);
+      break;
+    }
+
+    // Single step
+    // IN : $s
+    // OUT: A Stop-reply packet is sent later, when the execution halts.
+    case 's': {
+      if (status_ == Status::Suspended) {
+        gdb_server_->PrepareStep();
+        SetStatus(Status::Running);
+      }
+      return ProcessPacketResult::Continue;
+    }
+
+    // Find out if the thread 'id' is alive.
+    // IN : $T
+    // OUT: $OK if alive, $Enn if thread is dead.
+    case 'T': {
+      uint64_t id;
+      if (!pkt_in->GetNumberSep(&id, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+      if (id != kThreadId) {
+        err = ErrorCode::BadArgs;
+        break;
+      }
+      pkt_out->AddString("OK");
+      break;
+    }
+
+    // Z: Adds a breakpoint
+    // IN : $Z<type>,<addr>,<kind>
+    //      <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+    // OUT: $OK (success) or $Enn (error)
+    case 'Z': {
+      uint64_t breakpoint_type;
+      uint64_t breakpoint_address;
+      uint64_t breakpoint_kind;
+      // Only software breakpoints are supported.
+      if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+          !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+          !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+
+      wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+      if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(),
+                                      wasm_breakpoint_addr.Offset())) {
+        err = ErrorCode::Failed;
+        break;
+      }
+
+      pkt_out->AddString("OK");
+      break;
+    }
+
+    // z: Removes a breakpoint
+    // IN : $z<type>,<addr>,<kind>
+    //      <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+    // OUT: $OK (success) or $Enn (error)
+    case 'z': {
+      uint64_t breakpoint_type;
+      uint64_t breakpoint_address;
+      uint64_t breakpoint_kind;
+      if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+          !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+          !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+        err = ErrorCode::BadFormat;
+        break;
+      }
+
+      wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+      if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(),
+                                         wasm_breakpoint_addr.Offset())) {
+        err = ErrorCode::Failed;
+        break;
+      }
+
+      pkt_out->AddString("OK");
+      break;
+    }
+
+    // If the command is not recognized, ignore it by sending an empty reply.
+    default: {
+      TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
+    }
+  }
+
+  // If there is an error, return the error code instead of a payload
+  if (err != ErrorCode::None) {
+    pkt_out->Clear();
+    pkt_out->AddRawChar('E');
+    pkt_out->AddWord8(static_cast<uint8_t>(err));
+  }
+  return ProcessPacketResult::Paused;
+}
+
+Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
+                                             Packet* pkt_out) {
+  const char* str = &pkt_in->GetPayload()[1];
+
+  // Get first thread query
+  // IN : $qfThreadInfo
+  // OUT: $m<tid>
+  //
+  // Get next thread query
+  // IN : $qsThreadInfo
+  // OUT: $m<tid> or l to denote end of list.
+  if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) {
+    if (str[0] == 'f') {
+      pkt_out->AddString("m");
+      pkt_out->AddNumberSep(kThreadId, 0);
+    } else {
+      pkt_out->AddString("l");
+    }
+    return ErrorCode::None;
+  }
+
+  // Get a list of loaded libraries
+  // IN : $qXfer:libraries:read
+  // OUT: an XML document which lists loaded libraries, with this format:
+  // <library-list>
+  //   <library name="foo.wasm">
+  //     <section address="0x100000000"/>
+  //   </library>
+  //   <library name="bar.wasm">
+  //     <section address="0x200000000"/>
+  //   </library>
+  // </library-list>
+  // Note that LLDB must be compiled with libxml2 support to handle this packet.
+  std::string tmp = "Xfer:libraries:read";
+  if (!strncmp(str, tmp.data(), tmp.length())) {
+    std::vector<GdbServer::WasmModuleInfo> modules =
+        gdb_server_->GetLoadedModules();
+    std::string result("l<library-list>");
+    for (const auto& module : modules) {
+      wasm_addr_t address(module.module_id, 0);
+      char address_string[32];
+      snprintf(address_string, sizeof(address_string), "%" PRIu64,
+               static_cast<uint64_t>(address));
+      result += "<library name=\"";
+      result += module.module_name;
+      result += "\"><section address=\"";
+      result += address_string;
+      result += "\"/></library>";
+    }
+    result += "</library-list>";
+    pkt_out->AddString(result.c_str());
+    return ErrorCode::None;
+  }
+
+  // Get the current call stack.
+  // IN : $qWasmCallStack
+  // OUT: $xx..xxyy..yyzz..zz (A sequence of uint64_t values represented as
+  //                           consecutive 8-bytes blocks).
+  std::vector<std::string> toks = StringSplit(str, ":;");
+  if (toks[0] == "WasmCallStack") {
+    std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack();
+    std::vector<uint64_t> buffer;
+    for (wasm_addr_t pc : call_stack_pcs) {
+      buffer.push_back(pc);
+    }
+    pkt_out->AddBlock(buffer.data(),
+                      static_cast<uint32_t>(sizeof(uint64_t) * buffer.size()));
+    return ErrorCode::None;
+  }
+
+  // Get a Wasm global value in the Wasm module specified.
+  // IN : $qWasmGlobal:frame_index;index
+  // OUT: $xx..xx
+  if (toks[0] == "WasmGlobal") {
+    if (toks.size() == 3) {
+      uint32_t frame_index =
+          static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+      uint32_t index =
+          static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+      uint8_t buff[16];
+      uint32_t size = 0;
+      if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) {
+        pkt_out->AddBlock(buff, size);
+        return ErrorCode::None;
+      } else {
+        return ErrorCode::Failed;
+      }
+    }
+    return ErrorCode::BadFormat;
+  }
+
+  // Get a Wasm local value in the stack frame specified.
+  // IN : $qWasmLocal:frame_index;index
+  // OUT: $xx..xx
+  if (toks[0] == "WasmLocal") {
+    if (toks.size() == 3) {
+      uint32_t frame_index =
+          static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+      uint32_t index =
+          static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+      uint8_t buff[16];
+      uint32_t size = 0;
+      if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) {
+        pkt_out->AddBlock(buff, size);
+        return ErrorCode::None;
+      } else {
+        return ErrorCode::Failed;
+      }
+    }
+    return ErrorCode::BadFormat;
+  }
+
+  // Get a Wasm local from the operand stack at the index specified.
+  // IN : qWasmStackValue:frame_index;index
+  // OUT: $xx..xx
+  if (toks[0] == "WasmStackValue") {
+    if (toks.size() == 3) {
+      uint32_t frame_index =
+          static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+      uint32_t index =
+          static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+      uint8_t buff[16];
+      uint32_t size = 0;
+      if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) {
+        pkt_out->AddBlock(buff, size);
+        return ErrorCode::None;
+      } else {
+        return ErrorCode::Failed;
+      }
+    }
+    return ErrorCode::BadFormat;
+  }
+
+  // Read Wasm memory.
+  // IN : $qWasmMem:frame_index;addr;len
+  // OUT: $xx..xx
+  if (toks[0] == "WasmMem") {
+    if (toks.size() == 4) {
+      uint32_t frame_index =
+          static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+      uint32_t address =
+          static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 16));
+      uint32_t length =
+          static_cast<uint32_t>(strtol(toks[3].data(), nullptr, 16));
+      if (length > Transport::kBufSize / 2) {
+        return ErrorCode::BadArgs;
+      }
+      uint8_t buff[Transport::kBufSize];
+      uint32_t read =
+          gdb_server_->GetWasmMemory(frame_index, address, buff, length);
+      if (read > 0) {
+        pkt_out->AddBlock(buff, read);
+        return ErrorCode::None;
+      } else {
+        return ErrorCode::Failed;
+      }
+    }
+    return ErrorCode::BadFormat;
+  }
+
+  // No match so far, check the property cache.
+  QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]);
+  if (it != query_properties_.end()) {
+    pkt_out->AddString(it->second.data());
+  }
+  // If not found, just send an empty response.
+  return ErrorCode::None;
+}
+
+// A Stop-reply packet has the format:
+//   Sxx
+// or:
+//   Txx<name1>:<value1>;...;<nameN>:<valueN>
+// where 'xx' is a two-digit hex number that represents the stop signal
+// and the <name>:<value> pairs are used to report additional information,
+// like the thread id.
+void Target::SetStopReply(Packet* pkt_out) const {
+  pkt_out->AddRawChar('T');
+  pkt_out->AddWord8(cur_signal_);
+
+  // Adds 'thread-pcs:<pc1>,...,<pcN>;' A list of pc values for all threads that
+  // currently exist in the process.
+  char buff[64];
+  snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";",
+           static_cast<uint64_t>(GetCurrentPc()));
+  pkt_out->AddString(buff);
+
+  // Adds 'thread:<tid>;' pair. Note that a terminating ';' is required.
+  pkt_out->AddString("thread:");
+  pkt_out->AddNumberSep(kThreadId, ';');
+}
+
+void Target::SetStatus(Status status, int8_t signal,
+                       std::vector<wasm_addr_t> call_frames, Isolate* isolate) {
+  v8::base::MutexGuard guard(&mutex_);
+
+  DCHECK((status == Status::Suspended && signal != 0 &&
+          call_frames.size() > 0 && isolate != nullptr) ||
+         (status != Status::Suspended && signal == 0 &&
+          call_frames.size() == 0 && isolate == nullptr));
+
+  current_isolate_ = isolate;
+  status_ = status;
+  cur_signal_ = signal;
+  call_frames_ = call_frames;
+}
+
+const std::vector<wasm_addr_t> Target::GetCallStack() const {
+  v8::base::MutexGuard guard(&mutex_);
+
+  return call_frames_;
+}
+
+wasm_addr_t Target::GetCurrentPc() const {
+  v8::base::MutexGuard guard(&mutex_);
+
+  wasm_addr_t pc{0};
+  if (call_frames_.size() > 0) {
+    pc = call_frames_[0];
+  }
+  return pc;
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/target.h b/src/debug/wasm/gdb-server/target.h
new file mode 100644
index 0000000..1af81d3
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.h
@@ -0,0 +1,140 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+
+#include <atomic>
+#include <map>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+class Packet;
+class Session;
+
+// Class Target represents a debugging target. It contains the logic to decode
+// incoming GDB-remote packets, execute them forwarding the debugger commands
+// and queries to the Wasm engine, and send back GDB-remote packets.
+class Target {
+ public:
+  // Contruct a Target object.
+  explicit Target(GdbServer* gdb_server);
+
+  // This function spin on a debugging session, until it closes.
+  void Run(Session* ses);
+
+  void Terminate();
+  bool IsTerminated() const { return status_ == Status::Terminated; }
+
+  // Notifies that the debuggee thread suspended at a breakpoint.
+  void OnProgramBreak(Isolate* isolate,
+                      const std::vector<wasm_addr_t>& call_frames);
+  // Notifies that the debuggee thread suspended because of an unhandled
+  // exception.
+  void OnException(Isolate* isolate,
+                   const std::vector<wasm_addr_t>& call_frames);
+
+  // Returns the state at the moment of the thread suspension.
+  const std::vector<wasm_addr_t> GetCallStack() const;
+  wasm_addr_t GetCurrentPc() const;
+  Isolate* GetCurrentIsolate() const { return current_isolate_; }
+
+ private:
+  void OnSuspended(Isolate* isolate, int signal,
+                   const std::vector<wasm_addr_t>& call_frames);
+
+  // Initializes a map used to make fast lookups when handling query packets
+  // that have a constant response.
+  void InitQueryPropertyMap();
+
+  // Blocks waiting for one of these two events to occur:
+  // - A network packet arrives from the debugger, or the debugger connection is
+  //   closed;
+  // - The debuggee suspends execution because of a trap or breakpoint.
+  void WaitForDebugEvent();
+  void ProcessDebugEvent();
+
+  // Processes GDB-remote packets that arrive from the debugger.
+  // This method should be called when the debuggee has suspended its execution.
+  void ProcessCommands();
+
+  // Requests that the thread suspends execution at the next Wasm instruction.
+  void Suspend();
+
+  enum class ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+
+  enum class ProcessPacketResult {
+    Paused,    // The command was processed, debuggee still paused.
+    Continue,  // The debuggee should resume execution.
+    Detach,    // Request to detach from the debugger.
+    Kill       // Request to terminate the debuggee process.
+  };
+  // This function always succeedes, since all errors are reported as an error
+  // string "Exx" where xx is a two digit number.
+  // The return value indicates if the target can resume execution or it is
+  // still paused.
+  ProcessPacketResult ProcessPacket(Packet* pkt_in, Packet* pkt_out);
+
+  // Processes a general query packet
+  ErrorCode ProcessQueryPacket(const Packet* pkt_in, Packet* pkt_out);
+
+  // Formats a 'Stop-reply' packet, which is sent in response of a 'c'
+  // (continue), 's' (step) and '?' (query halt reason) commands.
+  void SetStopReply(Packet* pkt_out) const;
+
+  enum class Status { Running, WaitingForSuspension, Suspended, Terminated };
+
+  void SetStatus(Status status, int8_t signal = 0,
+                 std::vector<wasm_addr_t> call_frames_ = {},
+                 Isolate* isolate = nullptr);
+
+  GdbServer* gdb_server_;
+
+  std::atomic<Status> status_;
+
+  // Signal being processed.
+  std::atomic<int8_t> cur_signal_;
+
+  // Session object not owned by the Target.
+  Session* session_;
+
+  // Map used to make fast lookups when handling query packets.
+  typedef std::map<std::string, std::string> QueryPropertyMap;
+  QueryPropertyMap query_properties_;
+
+  bool debugger_initial_suspension_;
+
+  // Used to block waiting for suspension
+  v8::base::Semaphore semaphore_;
+
+  mutable v8::base::Mutex mutex_;
+  //////////////////////////////////////////////////////////////////////////////
+  // Protected by {mutex_}:
+
+  // Current isolate. This is not null only when the target is in a Suspended
+  // state and it is the isolate associated to the current call stack and used
+  // for all debugging activities.
+  Isolate* current_isolate_;
+
+  // Call stack when the execution is suspended.
+  std::vector<wasm_addr_t> call_frames_;
+
+  // End of fields protected by {mutex_}.
+  //////////////////////////////////////////////////////////////////////////////
+
+  DISALLOW_COPY_AND_ASSIGN(Target);
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
diff --git a/src/debug/wasm/gdb-server/transport.cc b/src/debug/wasm/gdb-server/transport.cc
new file mode 100644
index 0000000..f1aed96
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.cc
@@ -0,0 +1,457 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/transport.h"
+#include <fcntl.h>
+
+#ifndef SD_BOTH
+#define SD_BOTH 2
+#endif
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+SocketBinding::SocketBinding(SocketHandle socket_handle)
+    : socket_handle_(socket_handle) {}
+
+// static
+SocketBinding SocketBinding::Bind(uint16_t tcp_port) {
+  SocketHandle socket_handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+  if (socket_handle == InvalidSocket) {
+    TRACE_GDB_REMOTE("Failed to create socket.\n");
+    return SocketBinding(InvalidSocket);
+  }
+  struct sockaddr_in sockaddr;
+  // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+  memset(&sockaddr, 0, sizeof(sockaddr));
+  socklen_t addrlen = static_cast<socklen_t>(sizeof(sockaddr));
+  sockaddr.sin_family = AF_INET;
+  sockaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+  sockaddr.sin_port = htons(tcp_port);
+
+#if _WIN32
+  // On Windows, SO_REUSEADDR has a different meaning than on POSIX systems.
+  // SO_REUSEADDR allows hijacking of an open socket by another process.
+  // The SO_EXCLUSIVEADDRUSE flag prevents this behavior.
+  // See:
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx
+  //
+  // Additionally, unlike POSIX, TCP server sockets can be bound to
+  // ports in the TIME_WAIT state, without setting SO_REUSEADDR.
+  int exclusive_address = 1;
+  if (setsockopt(socket_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+                 reinterpret_cast<char*>(&exclusive_address),
+                 sizeof(exclusive_address))) {
+    TRACE_GDB_REMOTE("Failed to set SO_EXCLUSIVEADDRUSE option.\n");
+  }
+#else
+  // On POSIX, this is necessary to ensure that the TCP port is released
+  // promptly when sel_ldr exits.  Without this, the TCP port might
+  // only be released after a timeout, and later processes can fail
+  // to bind it.
+  int reuse_address = 1;
+  if (setsockopt(socket_handle, SOL_SOCKET, SO_REUSEADDR,
+                 reinterpret_cast<char*>(&reuse_address),
+                 sizeof(reuse_address))) {
+    TRACE_GDB_REMOTE("Failed to set SO_REUSEADDR option.\n");
+  }
+#endif
+
+  if (bind(socket_handle, reinterpret_cast<struct sockaddr*>(&sockaddr),
+           addrlen)) {
+    TRACE_GDB_REMOTE("Failed to bind server.\n");
+    return SocketBinding(InvalidSocket);
+  }
+
+  if (listen(socket_handle, 1)) {
+    TRACE_GDB_REMOTE("Failed to listen.\n");
+    return SocketBinding(InvalidSocket);
+  }
+  return SocketBinding(socket_handle);
+}
+
+std::unique_ptr<SocketTransport> SocketBinding::CreateTransport() {
+  return std::make_unique<SocketTransport>(socket_handle_);
+}
+
+uint16_t SocketBinding::GetBoundPort() {
+  struct sockaddr_in saddr;
+  struct sockaddr* psaddr = reinterpret_cast<struct sockaddr*>(&saddr);
+  // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+  memset(&saddr, 0, sizeof(saddr));
+  socklen_t addrlen = static_cast<socklen_t>(sizeof(saddr));
+  if (::getsockname(socket_handle_, psaddr, &addrlen)) {
+    TRACE_GDB_REMOTE("Failed to retrieve bound address.\n");
+    return 0;
+  }
+  return ntohs(saddr.sin_port);
+}
+
+// Do not delay sending small packets.  This significantly speeds up
+// remote debugging.  Debug stub uses buffering to send outgoing packets
+// so they are not split into more TCP packets than necessary.
+void DisableNagleAlgorithm(SocketHandle socket) {
+  int nodelay = 1;
+  if (::setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
+                   reinterpret_cast<char*>(&nodelay), sizeof(nodelay))) {
+    TRACE_GDB_REMOTE("Failed to set TCP_NODELAY option.\n");
+  }
+}
+
+Transport::Transport(SocketHandle s)
+    : buf_(new char[kBufSize]),
+      pos_(0),
+      size_(0),
+      handle_bind_(s),
+      handle_accept_(InvalidSocket) {}
+
+Transport::~Transport() {
+  if (handle_accept_ != InvalidSocket) {
+    CloseSocket(handle_accept_);
+  }
+}
+
+void Transport::CopyFromBuffer(char** dst, int32_t* len) {
+  int32_t copy_bytes = std::min(*len, size_ - pos_);
+  memcpy(*dst, buf_.get() + pos_, copy_bytes);
+  pos_ += copy_bytes;
+  *len -= copy_bytes;
+  *dst += copy_bytes;
+}
+
+bool Transport::Read(char* dst, int32_t len) {
+  if (pos_ < size_) {
+    CopyFromBuffer(&dst, &len);
+  }
+  while (len > 0) {
+    pos_ = 0;
+    size_ = 0;
+    if (!ReadSomeData()) {
+      return false;
+    }
+    CopyFromBuffer(&dst, &len);
+  }
+  return true;
+}
+
+bool Transport::Write(const char* src, int32_t len) {
+  while (len > 0) {
+    ssize_t result = ::send(handle_accept_, src, len, 0);
+    if (result > 0) {
+      src += result;
+      len -= result;
+      continue;
+    }
+    if (result == 0) {
+      return false;
+    }
+    if (SocketGetLastError() != kErrInterrupt) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Return true if there is data to read.
+bool Transport::IsDataAvailable() const {
+  if (pos_ < size_) {
+    return true;
+  }
+  fd_set fds;
+
+  FD_ZERO(&fds);
+  FD_SET(handle_accept_, &fds);
+
+  // We want a "non-blocking" check
+  struct timeval timeout;
+  timeout.tv_sec = 0;
+  timeout.tv_usec = 0;
+
+  // Check if this file handle can select on read
+  int cnt = select(static_cast<int>(handle_accept_) + 1, &fds, 0, 0, &timeout);
+
+  // If we are ready, or if there is an error.  We return true
+  // on error, to let the next IO request fail.
+  if (cnt != 0) return true;
+
+  return false;
+}
+
+void Transport::Close() {
+  ::shutdown(handle_bind_, SD_BOTH);
+  CloseSocket(handle_bind_);
+  Disconnect();
+}
+
+void Transport::Disconnect() {
+  if (handle_accept_ != InvalidSocket) {
+    // Shutdown the connection in both directions.  This should
+    // always succeed, and nothing we can do if this fails.
+    ::shutdown(handle_accept_, SD_BOTH);
+    CloseSocket(handle_accept_);
+    handle_accept_ = InvalidSocket;
+  }
+}
+
+#if _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+  socket_event_ = WSA_INVALID_EVENT;
+  faulted_thread_event_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
+  if (faulted_thread_event_ == NULL) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::SocketTransport: Failed to create event object for "
+        "faulted thread\n");
+  }
+}
+
+SocketTransport::~SocketTransport() {
+  if (!CloseHandle(faulted_thread_event_)) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::~SocketTransport: Failed to close "
+        "event\n");
+  }
+
+  if (socket_event_) {
+    if (!::WSACloseEvent(socket_event_)) {
+      TRACE_GDB_REMOTE(
+          "SocketTransport::~SocketTransport: Failed to close "
+          "socket event\n");
+    }
+  }
+}
+
+bool SocketTransport::AcceptConnection() {
+  CHECK(handle_accept_ == InvalidSocket);
+  handle_accept_ = ::accept(handle_bind_, NULL, 0);
+  if (handle_accept_ != InvalidSocket) {
+    DisableNagleAlgorithm(handle_accept_);
+
+    // Create socket event
+    socket_event_ = ::WSACreateEvent();
+    if (socket_event_ == WSA_INVALID_EVENT) {
+      TRACE_GDB_REMOTE(
+          "SocketTransport::AcceptConnection: Failed to create socket event\n");
+    }
+
+    // Listen for close events in order to handle them correctly.
+    // Additionally listen for read readiness as WSAEventSelect sets the socket
+    // to non-blocking mode.
+    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx
+    if (::WSAEventSelect(handle_accept_, socket_event_, FD_CLOSE | FD_READ) ==
+        SOCKET_ERROR) {
+      TRACE_GDB_REMOTE(
+          "SocketTransport::AcceptConnection: Failed to bind event to "
+          "socket\n");
+    }
+    return true;
+  }
+  return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+  while (true) {
+    ssize_t result =
+        ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+    if (result > 0) {
+      size_ += result;
+      return true;
+    }
+    if (result == 0) {
+      return false;  // The connection was gracefully closed.
+    }
+    // WSAEventSelect sets socket to non-blocking mode. This is essential
+    // for socket event notification to work, there is no workaround.
+    // See remarks section at the page
+    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+    if (SocketGetLastError() == WSAEWOULDBLOCK) {
+      if (::WaitForSingleObject(socket_event_, INFINITE) == WAIT_FAILED) {
+        TRACE_GDB_REMOTE(
+            "SocketTransport::ReadSomeData: Failed to wait on socket event\n");
+      }
+      if (!::ResetEvent(socket_event_)) {
+        TRACE_GDB_REMOTE(
+            "SocketTransport::ReadSomeData: Failed to reset socket event\n");
+      }
+      continue;
+    }
+
+    if (SocketGetLastError() != kErrInterrupt) {
+      return false;
+    }
+  }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+  // Don't wait if we already have data to read.
+  bool wait = !(pos_ < size_);
+
+  HANDLE handles[2];
+  handles[0] = faulted_thread_event_;
+  handles[1] = socket_event_;
+  int count = size_ < kBufSize ? 2 : 1;
+  int result =
+      WaitForMultipleObjects(count, handles, FALSE, wait ? INFINITE : 0);
+  if (result == WAIT_OBJECT_0 + 1) {
+    if (!ResetEvent(socket_event_)) {
+      TRACE_GDB_REMOTE(
+          "SocketTransport::WaitForDebugStubEvent: Failed to reset socket "
+          "event\n");
+    }
+    return;
+  } else if (result == WAIT_OBJECT_0) {
+    if (!ResetEvent(faulted_thread_event_)) {
+      TRACE_GDB_REMOTE(
+          "SocketTransport::WaitForDebugStubEvent: Failed to reset event\n");
+    }
+    return;
+  } else if (result == WAIT_TIMEOUT) {
+    return;
+  }
+  TRACE_GDB_REMOTE(
+      "SocketTransport::WaitForDebugStubEvent: Wait for events failed\n");
+}
+
+bool SocketTransport::SignalThreadEvent() {
+  if (!SetEvent(faulted_thread_event_)) {
+    return false;
+  }
+  return true;
+}
+
+void SocketTransport::Disconnect() {
+  Transport::Disconnect();
+
+  if (socket_event_ != WSA_INVALID_EVENT && !::WSACloseEvent(socket_event_)) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::~SocketTransport: Failed to close "
+        "socket event\n");
+  }
+  socket_event_ = WSA_INVALID_EVENT;
+  SignalThreadEvent();
+}
+
+#else  // _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+  int fds[2];
+#if defined(__linux__)
+  int ret = pipe2(fds, O_CLOEXEC);
+#else
+  int ret = pipe(fds);
+#endif
+  if (ret < 0) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::SocketTransport: Failed to allocate pipe for faulted "
+        "thread\n");
+  }
+  faulted_thread_fd_read_ = fds[0];
+  faulted_thread_fd_write_ = fds[1];
+}
+
+SocketTransport::~SocketTransport() {
+  if (close(faulted_thread_fd_read_) != 0) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::~SocketTransport: Failed to close "
+        "event\n");
+  }
+  if (close(faulted_thread_fd_write_) != 0) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::~SocketTransport: Failed to close "
+        "event\n");
+  }
+}
+
+bool SocketTransport::AcceptConnection() {
+  CHECK(handle_accept_ == InvalidSocket);
+  handle_accept_ = ::accept(handle_bind_, NULL, 0);
+  if (handle_accept_ != InvalidSocket) {
+    DisableNagleAlgorithm(handle_accept_);
+    return true;
+  }
+  return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+  while (true) {
+    ssize_t result =
+        ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+    if (result > 0) {
+      size_ += result;
+      return true;
+    }
+    if (result == 0) {
+      return false;  // The connection was gracefully closed.
+    }
+    if (SocketGetLastError() != kErrInterrupt) {
+      return false;
+    }
+  }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+  // Don't wait if we already have data to read.
+  bool wait = !(pos_ < size_);
+
+  fd_set fds;
+  FD_ZERO(&fds);
+  FD_SET(faulted_thread_fd_read_, &fds);
+  int max_fd = faulted_thread_fd_read_;
+  if (size_ < kBufSize) {
+    FD_SET(handle_accept_, &fds);
+    max_fd = std::max(max_fd, handle_accept_);
+  }
+
+  int ret;
+  // We don't need sleep-polling on Linux now, so we set either zero or infinite
+  // timeout.
+  if (wait) {
+    ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
+  } else {
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+    ret = select(max_fd + 1, &fds, NULL, NULL, &timeout);
+  }
+  if (ret < 0) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport::WaitForDebugStubEvent: Failed to wait for "
+        "debug stub event\n");
+  }
+
+  if (ret > 0) {
+    if (FD_ISSET(faulted_thread_fd_read_, &fds)) {
+      char buf[16];
+      if (read(faulted_thread_fd_read_, &buf, sizeof(buf)) < 0) {
+        TRACE_GDB_REMOTE(
+            "SocketTransport::WaitForDebugStubEvent: Failed to read from "
+            "debug stub event pipe fd\n");
+      }
+    }
+    if (FD_ISSET(handle_accept_, &fds)) ReadSomeData();
+  }
+}
+
+bool SocketTransport::SignalThreadEvent() {
+  // Notify the debug stub by marking the thread as faulted.
+  char buf = 0;
+  if (write(faulted_thread_fd_write_, &buf, sizeof(buf)) != sizeof(buf)) {
+    TRACE_GDB_REMOTE(
+        "SocketTransport:SignalThreadEvent: Can't send debug stub "
+        "event\n");
+    return false;
+  }
+  return true;
+}
+
+#endif  // _WIN32
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#undef SD_BOTH
diff --git a/src/debug/wasm/gdb-server/transport.h b/src/debug/wasm/gdb-server/transport.h
new file mode 100644
index 0000000..42bf438
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.h
@@ -0,0 +1,193 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+
+#include <sstream>
+#include <vector>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+#if _WIN32
+#include <windows.h>
+#include <winsock2.h>
+
+typedef SOCKET SocketHandle;
+
+#define CloseSocket closesocket
+#define InvalidSocket INVALID_SOCKET
+#define SocketGetLastError() WSAGetLastError()
+static const int kErrInterrupt = WSAEINTR;
+typedef int ssize_t;
+typedef int socklen_t;
+
+#else  // _WIN32
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+
+typedef int SocketHandle;
+
+#define CloseSocket close
+#define InvalidSocket (-1)
+#define SocketGetLastError() errno
+static const int kErrInterrupt = EINTR;
+
+#endif  // _WIN32
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class SocketTransport;
+
+// Acts as a factory for Transport objects bound to a specified TCP port.
+class SocketBinding {
+ public:
+  // Wrap existing socket handle.
+  explicit SocketBinding(SocketHandle socket_handle);
+
+  // Bind to the specified TCP port.
+  static SocketBinding Bind(uint16_t tcp_port);
+
+  bool IsValid() const { return socket_handle_ != InvalidSocket; }
+
+  // Create a transport object from this socket binding
+  std::unique_ptr<SocketTransport> CreateTransport();
+
+  // Get port the socket is bound to.
+  uint16_t GetBoundPort();
+
+ private:
+  SocketHandle socket_handle_;
+};
+
+class V8_EXPORT_PRIVATE TransportBase {
+ public:
+  virtual ~TransportBase() {}
+
+  // Waits for an incoming connection on the bound port.
+  virtual bool AcceptConnection() = 0;
+
+  // Read {len} bytes from this transport, possibly blocking until enough data
+  // is available.
+  // {dst} must point to a buffer large enough to contain {len} bytes.
+  // Returns true on success.
+  // Returns false if the connection is closed; in that case the {dst} may have
+  // been partially overwritten.
+  virtual bool Read(char* dst, int32_t len) = 0;
+
+  // Write {len} bytes to this transport.
+  // Return true on success, false if the connection is closed.
+  virtual bool Write(const char* src, int32_t len) = 0;
+
+  // Return true if there is data to read.
+  virtual bool IsDataAvailable() const = 0;
+
+  // If we are connected to a debugger, gracefully closes the connection.
+  // This should be called when a debugging session gets closed.
+  virtual void Disconnect() = 0;
+
+  // Shuts down this transport, gracefully closing the existing connection and
+  // also closing the listening socket. This should be called when the GDB stub
+  // shuts down, when the program terminates.
+  virtual void Close() = 0;
+
+  // Blocks waiting for one of these two events to occur:
+  // - A network event (a new packet arrives, or the connection is dropped),
+  // - A thread event is signaled (the execution stopped because of a trap or
+  // breakpoint).
+  virtual void WaitForDebugStubEvent() = 0;
+
+  // Signal that this transport should leave an alertable wait state because
+  // the execution of the debuggee was stopped because of a trap or breakpoint.
+  virtual bool SignalThreadEvent() = 0;
+};
+
+class Transport : public TransportBase {
+ public:
+  explicit Transport(SocketHandle s);
+  ~Transport() override;
+
+  // TransportBase
+  bool Read(char* dst, int32_t len) override;
+  bool Write(const char* src, int32_t len) override;
+  bool IsDataAvailable() const override;
+  void Disconnect() override;
+  void Close() override;
+
+  static const int kBufSize = 4096;
+
+ protected:
+  // Copy buffered data to *dst up to len bytes and update dst and len.
+  void CopyFromBuffer(char** dst, int32_t* len);
+
+  // Read available data from the socket. Return false on EOF or error.
+  virtual bool ReadSomeData() = 0;
+
+  std::unique_ptr<char[]> buf_;
+  int32_t pos_;
+  int32_t size_;
+  SocketHandle handle_bind_;
+  SocketHandle handle_accept_;
+};
+
+#if _WIN32
+
+class SocketTransport : public Transport {
+ public:
+  explicit SocketTransport(SocketHandle s);
+  ~SocketTransport() override;
+
+  // TransportBase
+  bool AcceptConnection() override;
+  void Disconnect() override;
+  void WaitForDebugStubEvent() override;
+  bool SignalThreadEvent() override;
+
+ private:
+  bool ReadSomeData() override;
+
+  HANDLE socket_event_;
+  HANDLE faulted_thread_event_;
+
+  DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#else  // _WIN32
+
+class SocketTransport : public Transport {
+ public:
+  explicit SocketTransport(SocketHandle s);
+  ~SocketTransport() override;
+
+  // TransportBase
+  bool AcceptConnection() override;
+  void WaitForDebugStubEvent() override;
+  bool SignalThreadEvent() override;
+
+ private:
+  bool ReadSomeData() override;
+
+  int faulted_thread_fd_read_;
+  int faulted_thread_fd_write_;
+
+  DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#endif  // _WIN32
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.cc b/src/debug/wasm/gdb-server/wasm-module-debug.cc
new file mode 100644
index 0000000..f0b77bc
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.cc
@@ -0,0 +1,387 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
+
+#include "src/api/api-inl.h"
+#include "src/api/api.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/frames.h"
+#include "src/objects/script.h"
+#include "src/wasm/wasm-debug.h"
+#include "src/wasm/wasm-value.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+WasmModuleDebug::WasmModuleDebug(v8::Isolate* isolate,
+                                 Local<debug::WasmScript> wasm_script) {
+  DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
+
+  isolate_ = isolate;
+  wasm_script_ = Global<debug::WasmScript>(isolate, wasm_script);
+}
+
+std::string WasmModuleDebug::GetModuleName() const {
+  v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+  v8::Local<v8::String> name;
+  std::string module_name;
+  if (wasm_script->Name().ToLocal(&name)) {
+    module_name = *(v8::String::Utf8Value(isolate_, name));
+  }
+  return module_name;
+}
+
+Handle<WasmInstanceObject> WasmModuleDebug::GetFirstWasmInstance() {
+  v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+  Handle<Script> script = Utils::OpenHandle(*wasm_script);
+
+  Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(),
+                                           GetIsolate());
+  if (weak_instance_list->length() > 0) {
+    MaybeObject maybe_instance = weak_instance_list->Get(0);
+    if (maybe_instance->IsWeak()) {
+      Handle<WasmInstanceObject> instance(
+          WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()),
+          GetIsolate());
+      return instance;
+    }
+  }
+  return Handle<WasmInstanceObject>::null();
+}
+
+int GetLEB128Size(Vector<const uint8_t> module_bytes, int offset) {
+  int index = offset;
+  while (module_bytes[index] & 0x80) index++;
+  return index + 1 - offset;
+}
+
+int ReturnPc(const NativeModule* native_module, int pc) {
+  Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
+  uint8_t opcode = wire_bytes[pc];
+  switch (opcode) {
+    case kExprCallFunction: {
+      // skip opcode
+      pc++;
+      // skip function index
+      return pc + GetLEB128Size(wire_bytes, pc);
+    }
+    case kExprCallIndirect: {
+      // skip opcode
+      pc++;
+      // skip signature index
+      pc += GetLEB128Size(wire_bytes, pc);
+      // skip table index
+      return pc + GetLEB128Size(wire_bytes, pc);
+    }
+    default:
+      UNREACHABLE();
+  }
+}
+
+// static
+std::vector<wasm_addr_t> WasmModuleDebug::GetCallStack(
+    uint32_t debug_context_id, Isolate* isolate) {
+  std::vector<wasm_addr_t> call_stack;
+  for (StackFrameIterator frame_it(isolate); !frame_it.done();
+       frame_it.Advance()) {
+    StackFrame* const frame = frame_it.frame();
+    switch (frame->type()) {
+      case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+      case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+      case StackFrame::OPTIMIZED:
+      case StackFrame::INTERPRETED:
+      case StackFrame::BUILTIN:
+      case StackFrame::WASM: {
+        // A standard frame may include many summarized frames, due to inlining.
+        std::vector<FrameSummary> frames;
+        CommonFrame::cast(frame)->Summarize(&frames);
+        for (size_t i = frames.size(); i-- != 0;) {
+          int offset = 0;
+          Handle<Script> script;
+
+          auto& summary = frames[i];
+          if (summary.IsJavaScript()) {
+            FrameSummary::JavaScriptFrameSummary const& java_script =
+                summary.AsJavaScript();
+            offset = java_script.code_offset();
+            script = Handle<Script>::cast(java_script.script());
+          } else if (summary.IsWasm()) {
+            FrameSummary::WasmFrameSummary const& wasm = summary.AsWasm();
+            offset = GetWasmFunctionOffset(wasm.wasm_instance()->module(),
+                                           wasm.function_index()) +
+                     wasm.byte_offset();
+            script = wasm.script();
+
+            bool zeroth_frame = call_stack.empty();
+            if (!zeroth_frame) {
+              const NativeModule* native_module =
+                  wasm.wasm_instance()->module_object().native_module();
+              offset = ReturnPc(native_module, offset);
+            }
+          }
+
+          if (offset > 0) {
+            call_stack.push_back(
+                {debug_context_id << 16 | script->id(), uint32_t(offset)});
+          }
+        }
+        break;
+      }
+
+      case StackFrame::BUILTIN_EXIT:
+      default:
+        // ignore the frame.
+        break;
+    }
+  }
+  if (call_stack.empty()) call_stack.push_back({1, 0});
+  return call_stack;
+}
+
+// static
+std::vector<FrameSummary> WasmModuleDebug::FindWasmFrame(
+    StackTraceFrameIterator* frame_it, uint32_t* frame_index) {
+  while (!frame_it->done()) {
+    StackFrame* const frame = frame_it->frame();
+    switch (frame->type()) {
+      case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+      case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+      case StackFrame::OPTIMIZED:
+      case StackFrame::INTERPRETED:
+      case StackFrame::BUILTIN:
+      case StackFrame::WASM: {
+        // A standard frame may include many summarized frames, due to inlining.
+        std::vector<FrameSummary> frames;
+        CommonFrame::cast(frame)->Summarize(&frames);
+        const size_t frame_count = frames.size();
+        DCHECK_GT(frame_count, 0);
+
+        if (frame_count > *frame_index) {
+          if (frame_it->is_wasm())
+            return frames;
+          else
+            return {};
+        } else {
+          *frame_index -= frame_count;
+          frame_it->Advance();
+        }
+        break;
+      }
+
+      case StackFrame::BUILTIN_EXIT:
+      default:
+        // ignore the frame.
+        break;
+    }
+  }
+  return {};
+}
+
+// static
+Handle<WasmInstanceObject> WasmModuleDebug::GetWasmInstance(
+    Isolate* isolate, uint32_t frame_index) {
+  StackTraceFrameIterator frame_it(isolate);
+  std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+  if (frames.empty()) {
+    return Handle<WasmInstanceObject>::null();
+  }
+
+  int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+  const FrameSummary::WasmFrameSummary& summary =
+      frames[reversed_index].AsWasm();
+  return summary.wasm_instance();
+}
+
+// static
+bool WasmModuleDebug::GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+                                    uint32_t index, uint8_t* buffer,
+                                    uint32_t buffer_size, uint32_t* size) {
+  HandleScope handles(isolate);
+
+  Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+  if (!instance.is_null()) {
+    Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
+    const wasm::WasmModule* module = module_object->module();
+    if (index < module->globals.size()) {
+      wasm::WasmValue wasm_value =
+          WasmInstanceObject::GetGlobalValue(instance, module->globals[index]);
+      return GetWasmValue(wasm_value, buffer, buffer_size, size);
+    }
+  }
+  return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+                                   uint32_t index, uint8_t* buffer,
+                                   uint32_t buffer_size, uint32_t* size) {
+  HandleScope handles(isolate);
+
+  StackTraceFrameIterator frame_it(isolate);
+  std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+  if (frames.empty()) {
+    return false;
+  }
+
+  int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+  const FrameSummary& summary = frames[reversed_index];
+  if (summary.IsWasm()) {
+    Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+    if (!instance.is_null()) {
+      Handle<WasmModuleObject> module_object(instance->module_object(),
+                                             isolate);
+      wasm::NativeModule* native_module = module_object->native_module();
+      DebugInfo* debug_info = native_module->GetDebugInfo();
+      if (static_cast<uint32_t>(debug_info->GetNumLocals(
+              isolate, frame_it.frame()->pc())) > index) {
+        wasm::WasmValue wasm_value = debug_info->GetLocalValue(
+            index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+            frame_it.frame()->callee_fp());
+        return GetWasmValue(wasm_value, buffer, buffer_size, size);
+      }
+    }
+  }
+  return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+                                        uint32_t index, uint8_t* buffer,
+                                        uint32_t buffer_size, uint32_t* size) {
+  HandleScope handles(isolate);
+
+  StackTraceFrameIterator frame_it(isolate);
+  std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+  if (frames.empty()) {
+    return false;
+  }
+
+  int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+  const FrameSummary& summary = frames[reversed_index];
+  if (summary.IsWasm()) {
+    Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+    if (!instance.is_null()) {
+      Handle<WasmModuleObject> module_object(instance->module_object(),
+                                             isolate);
+      wasm::NativeModule* native_module = module_object->native_module();
+      DebugInfo* debug_info = native_module->GetDebugInfo();
+      if (static_cast<uint32_t>(debug_info->GetStackDepth(
+              isolate, frame_it.frame()->pc())) > index) {
+        WasmValue wasm_value = debug_info->GetStackValue(
+            index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+            frame_it.frame()->callee_fp());
+        return GetWasmValue(wasm_value, buffer, buffer_size, size);
+      }
+    }
+  }
+  return false;
+}
+
+// static
+uint32_t WasmModuleDebug::GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+                                        uint32_t offset, uint8_t* buffer,
+                                        uint32_t size) {
+  HandleScope handles(isolate);
+
+  uint32_t bytes_read = 0;
+  Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+  if (!instance.is_null()) {
+    uint8_t* mem_start = instance->memory_start();
+    size_t mem_size = instance->memory_size();
+    if (static_cast<uint64_t>(offset) + size <= mem_size) {
+      memcpy(buffer, mem_start + offset, size);
+      bytes_read = size;
+    } else if (offset < mem_size) {
+      bytes_read = static_cast<uint32_t>(mem_size) - offset;
+      memcpy(buffer, mem_start + offset, bytes_read);
+    }
+  }
+  return bytes_read;
+}
+
+uint32_t WasmModuleDebug::GetWasmModuleBytes(wasm_addr_t wasm_addr,
+                                             uint8_t* buffer, uint32_t size) {
+  uint32_t bytes_read = 0;
+  // Any instance will work.
+  Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
+  if (!instance.is_null()) {
+    Handle<WasmModuleObject> module_object(instance->module_object(),
+                                           GetIsolate());
+    wasm::NativeModule* native_module = module_object->native_module();
+    const wasm::ModuleWireBytes wire_bytes(native_module->wire_bytes());
+    uint32_t offset = wasm_addr.Offset();
+    if (offset < wire_bytes.length()) {
+      uint32_t module_size = static_cast<uint32_t>(wire_bytes.length());
+      bytes_read = module_size - offset >= size ? size : module_size - offset;
+      memcpy(buffer, wire_bytes.start() + offset, bytes_read);
+    }
+  }
+  return bytes_read;
+}
+
+bool WasmModuleDebug::AddBreakpoint(uint32_t offset, int* breakpoint_id) {
+  v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+  Handle<Script> script = Utils::OpenHandle(*wasm_script);
+  Handle<String> condition = GetIsolate()->factory()->empty_string();
+  int breakpoint_address = static_cast<int>(offset);
+  return GetIsolate()->debug()->SetBreakPointForScript(
+      script, condition, &breakpoint_address, breakpoint_id);
+}
+
+void WasmModuleDebug::RemoveBreakpoint(uint32_t offset, int breakpoint_id) {
+  v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+  Handle<Script> script = Utils::OpenHandle(*wasm_script);
+  GetIsolate()->debug()->RemoveBreakpointForWasmScript(script, breakpoint_id);
+}
+
+void WasmModuleDebug::PrepareStep() {
+  i::Isolate* isolate = GetIsolate();
+  DebugScope debug_scope(isolate->debug());
+  debug::PrepareStep(reinterpret_cast<v8::Isolate*>(isolate),
+                     debug::StepAction::StepIn);
+}
+
+template <typename T>
+bool StoreValue(const T& value, uint8_t* buffer, uint32_t buffer_size,
+                uint32_t* size) {
+  *size = sizeof(value);
+  if (*size > buffer_size) return false;
+  memcpy(buffer, &value, *size);
+  return true;
+}
+
+// static
+bool WasmModuleDebug::GetWasmValue(const wasm::WasmValue& wasm_value,
+                                   uint8_t* buffer, uint32_t buffer_size,
+                                   uint32_t* size) {
+  switch (wasm_value.type().kind()) {
+    case wasm::kWasmI32.kind():
+      return StoreValue(wasm_value.to_i32(), buffer, buffer_size, size);
+    case wasm::kWasmI64.kind():
+      return StoreValue(wasm_value.to_i64(), buffer, buffer_size, size);
+    case wasm::kWasmF32.kind():
+      return StoreValue(wasm_value.to_f32(), buffer, buffer_size, size);
+    case wasm::kWasmF64.kind():
+      return StoreValue(wasm_value.to_f64(), buffer, buffer_size, size);
+    case wasm::kWasmS128.kind():
+      return StoreValue(wasm_value.to_s128(), buffer, buffer_size, size);
+
+    case wasm::kWasmStmt.kind():
+    case wasm::kWasmExternRef.kind():
+    case wasm::kWasmFuncRef.kind():
+    case wasm::kWasmExnRef.kind():
+    case wasm::kWasmBottom.kind():
+    default:
+      // Not supported
+      return false;
+  }
+}
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.h b/src/debug/wasm/gdb-server/wasm-module-debug.h
new file mode 100644
index 0000000..10e6a5d
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.h
@@ -0,0 +1,105 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+#define V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+
+#include "src/debug/debug.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/execution/frames.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+
+class WasmValue;
+
+namespace gdb_server {
+
+// Represents the interface to access the Wasm engine state for a given module.
+// For the moment it only works with interpreted functions, in the future it
+// could be extended to also support Liftoff.
+class WasmModuleDebug {
+ public:
+  WasmModuleDebug(v8::Isolate* isolate, Local<debug::WasmScript> script);
+
+  std::string GetModuleName() const;
+  i::Isolate* GetIsolate() const {
+    return reinterpret_cast<i::Isolate*>(isolate_);
+  }
+
+  // Gets the value of the {index}th global value.
+  static bool GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+                            uint32_t index, uint8_t* buffer,
+                            uint32_t buffer_size, uint32_t* size);
+
+  // Gets the value of the {index}th local value in the {frame_index}th stack
+  // frame.
+  static bool GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+                           uint32_t index, uint8_t* buffer,
+                           uint32_t buffer_size, uint32_t* size);
+
+  // Gets the value of the {index}th value in the operand stack.
+  static bool GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+                                uint32_t index, uint8_t* buffer,
+                                uint32_t buffer_size, uint32_t* size);
+
+  // Reads {size} bytes, starting from {offset}, from the Memory instance
+  // associated to this module.
+  // Returns the number of byte copied to {buffer}, or 0 is case of error.
+  // Note: only one Memory for Module is currently supported.
+  static uint32_t GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+                                uint32_t offset, uint8_t* buffer,
+                                uint32_t size);
+
+  // Gets {size} bytes, starting from {offset}, from the Code space of this
+  // module.
+  // Returns the number of byte copied to {buffer}, or 0 is case of error.
+  uint32_t GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
+                              uint32_t size);
+
+  // Inserts a breakpoint at the offset {offset} of this module.
+  // Returns {true} if the breakpoint was successfully added.
+  bool AddBreakpoint(uint32_t offset, int* breakpoint_id);
+
+  // Removes a breakpoint at the offset {offset} of the this module.
+  void RemoveBreakpoint(uint32_t offset, int breakpoint_id);
+
+  // Handle stepping in wasm functions via the wasm interpreter.
+  void PrepareStep();
+
+  // Returns the current stack trace as a vector of instruction pointers.
+  static std::vector<wasm_addr_t> GetCallStack(uint32_t debug_context_id,
+                                               Isolate* isolate);
+
+ private:
+  // Returns the module WasmInstance associated to the {frame_index}th frame
+  // in the call stack.
+  static Handle<WasmInstanceObject> GetWasmInstance(Isolate* isolate,
+                                                    uint32_t frame_index);
+
+  // Returns its first WasmInstance for this Wasm module.
+  Handle<WasmInstanceObject> GetFirstWasmInstance();
+
+  // Iterates on current stack frames and return frame information for the
+  // {frame_index} specified.
+  // Returns an empty array if the frame specified does not correspond to a Wasm
+  // stack frame.
+  static std::vector<FrameSummary> FindWasmFrame(
+      StackTraceFrameIterator* frame_it, uint32_t* frame_index);
+
+  // Converts a WasmValue into an array of bytes.
+  static bool GetWasmValue(const wasm::WasmValue& wasm_value, uint8_t* buffer,
+                           uint32_t buffer_size, uint32_t* size);
+
+  v8::Isolate* isolate_;
+  Global<debug::WasmScript> wasm_script_;
+};
+
+}  // namespace gdb_server
+}  // namespace wasm
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_