[Lldb-commits] [lldb] [lldb] Implement WebAssembly debugging (PR #77949)

Paolo Severini via lldb-commits lldb-commits at lists.llvm.org
Mon Jan 22 05:58:34 PST 2024


https://github.com/paolosevMSFT updated https://github.com/llvm/llvm-project/pull/77949

>From 30d932bb0988e1c78c3e023be2270259df5e6664 Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Fri, 12 Jan 2024 09:10:59 -0800
Subject: [PATCH 1/3] Add support for source-level debugging of WebAssembly
 code

---
 lldb/include/lldb/Target/Process.h            |  40 +++
 lldb/source/Core/Value.cpp                    |   2 +-
 lldb/source/Expression/DWARFExpression.cpp    |  41 +++
 .../source/Interpreter/CommandInterpreter.cpp |  18 ++
 lldb/source/Plugins/Process/CMakeLists.txt    |   1 +
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |   7 +-
 .../Process/gdb-remote/ProcessGDBRemote.h     |   2 +
 .../Plugins/Process/wasm/CMakeLists.txt       |  12 +
 .../Plugins/Process/wasm/ProcessWasm.cpp      | 291 ++++++++++++++++++
 .../source/Plugins/Process/wasm/ProcessWasm.h | 129 ++++++++
 .../Plugins/Process/wasm/ThreadWasm.cpp       |  55 ++++
 lldb/source/Plugins/Process/wasm/ThreadWasm.h |  44 +++
 .../Plugins/Process/wasm/UnwindWasm.cpp       |  76 +++++
 lldb/source/Plugins/Process/wasm/UnwindWasm.h |  55 ++++
 .../Process/wasm/wasmRegisterContext.cpp      | 108 +++++++
 .../Process/wasm/wasmRegisterContext.h        |  75 +++++
 16 files changed, 954 insertions(+), 2 deletions(-)
 create mode 100644 lldb/source/Plugins/Process/wasm/CMakeLists.txt
 create mode 100644 lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
 create mode 100644 lldb/source/Plugins/Process/wasm/ProcessWasm.h
 create mode 100644 lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
 create mode 100644 lldb/source/Plugins/Process/wasm/ThreadWasm.h
 create mode 100644 lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
 create mode 100644 lldb/source/Plugins/Process/wasm/UnwindWasm.h
 create mode 100644 lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
 create mode 100644 lldb/source/Plugins/Process/wasm/wasmRegisterContext.h

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 24c599e044c78f..587ae085b479b7 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1548,6 +1548,46 @@ class Process : public std::enable_shared_from_this<Process>,
   virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                             Status &error);
 
+  /// Read of memory from a process.
+  ///
+  /// This function will read memory from the current process's address space
+  /// and remove any traps that may have been inserted into the memory.
+  ///
+  /// This overloads accepts an ExecutionContext as additional argument. By
+  /// default, it calls the previous overload without the ExecutionContext
+  /// argument, but it can be overridden by Process subclasses.
+  ///
+  /// \param[in] vm_addr
+  ///     A virtual load address that indicates where to start reading
+  ///     memory from.
+  ///
+  /// \param[out] buf
+  ///     A byte buffer that is at least \a size bytes long that
+  ///     will receive the memory bytes.
+  ///
+  /// \param[in] size
+  ///     The number of bytes to read.
+  ///
+  /// \param[in] exe_ctx
+  ///    The current execution context, if available.
+  ///
+  /// \param[out] error
+  ///     An error that indicates the success or failure of this
+  ///     operation. If error indicates success (error.Success()),
+  ///     then the value returned can be trusted, otherwise zero
+  ///     will be returned.
+  ///
+  /// \return
+  ///     The number of bytes that were actually read into \a buf. If
+  ///     the returned number is greater than zero, yet less than \a
+  ///     size, then this function will get called again with \a
+  ///     vm_addr, \a buf, and \a size updated appropriately. Zero is
+  ///     returned in the case of an error.
+  virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                            ExecutionContext *exe_ctx, Status &error) {
+    return ReadMemory(vm_addr, buf, size, error);
+  }
+
   /// Read of memory from a process.
   ///
   /// This function has the same semantics of ReadMemory except that it
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 995cc934c82044..47a5fdee773886 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -552,7 +552,7 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
 
         if (process) {
           const size_t bytes_read =
-              process->ReadMemory(address, dst, byte_size, error);
+              process->ReadMemory(address, dst, byte_size, exe_ctx, error);
           if (bytes_read != byte_size)
             error.SetErrorStringWithFormat(
                 "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index fe4928d4f43a43..ca24611724dc7c 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -346,6 +346,16 @@ static offset_t GetOpcodeDataSize(const DataExtractor &data,
     return (offset - data_offset) + subexpr_len;
   }
 
+  case DW_OP_WASM_location: {
+    uint8_t wasm_op = data.GetU8(&offset);
+    if (wasm_op == 3) {
+      data.GetU32(&offset);
+    } else {
+      data.GetULEB128(&offset);
+    }
+    return offset - data_offset;
+  }
+
   default:
     if (!dwarf_cu) {
       return LLDB_INVALID_OFFSET;
@@ -2595,6 +2605,37 @@ bool DWARFExpression::Evaluate(
       break;
     }
 
+    case DW_OP_WASM_location: {
+      uint8_t wasm_op = opcodes.GetU8(&offset);
+      uint32_t index;
+
+      /* LLDB doesn't have an address space to represents WebAssembly locals,
+       * globals and operand stacks.
+       * We encode these elements into virtual registers:
+       *   | tag: 2 bits | index: 30 bits |
+       *   where tag is:
+       *    0: Not a WebAssembly location
+       *    1: Local
+       *    2: Global
+       *    3: Operand stack value
+       */
+      if (wasm_op == 3) {
+        index = opcodes.GetU32(&offset);
+        wasm_op = 1;
+      } else {
+        index = opcodes.GetULEB128(&offset);
+      }
+
+      reg_num = (((wasm_op + 1) & 0x03) << 30) | (index & 0x3fffffff);
+
+      if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, tmp))
+        stack.push_back(tmp);
+      else
+        return false;
+
+      break;
+    }
+
     default:
       if (dwarf_cu) {
         if (dwarf_cu->GetSymbolFileDWARF().ParseVendorDWARFOpcode(
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 00651df48b6224..bcacc7aabb66ca 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -794,6 +794,24 @@ void CommandInterpreter::LoadCommandDictionary() {
     }
   }
 
+  std::unique_ptr<CommandObjectRegexCommand> connect_wasm_cmd_up(
+      new CommandObjectRegexCommand(
+          *this, "wasm",
+          "Connect to a WebAssembly process via remote GDB server.  "
+          "If no host is specifed, localhost is assumed.",
+          "wasm [<hostname>:]<portnum>", 0, false));
+  if (connect_wasm_cmd_up) {
+    if (connect_wasm_cmd_up->AddRegexCommand(
+            "^([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)$",
+            "process connect --plugin wasm connect://%1:%2") &&
+        connect_wasm_cmd_up->AddRegexCommand(
+            "^([[:digit:]]+)$",
+            "process connect --plugin wasm connect://localhost:%1")) {
+      CommandObjectSP command_sp(connect_wasm_cmd_up.release());
+      m_command_dict[std::string(command_sp->GetCommandName())] = command_sp;
+    }
+  }
+
   std::unique_ptr<CommandObjectRegexCommand> connect_kdp_remote_cmd_up(
       new CommandObjectRegexCommand(
           *this, "kdp-remote",
diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt
index a51d0f7afd1759..be109a303e8669 100644
--- a/lldb/source/Plugins/Process/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/CMakeLists.txt
@@ -19,3 +19,4 @@ add_subdirectory(elf-core)
 add_subdirectory(mach-core)
 add_subdirectory(minidump)
 add_subdirectory(FreeBSDKernel)
+add_subdirectory(wasm)
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 316be471df9295..674bbc9ff4fd0b 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1628,6 +1628,11 @@ void ProcessGDBRemote::ParseExpeditedRegisters(
   }
 }
 
+std::shared_ptr<ThreadGDBRemote>
+ProcessGDBRemote::CreateThread(lldb::tid_t tid) {
+  return std::make_shared<ThreadGDBRemote>(*this, tid);
+}
+
 ThreadSP ProcessGDBRemote::SetThreadStopInfo(
     lldb::tid_t tid, ExpeditedRegisterMap &expedited_register_map,
     uint8_t signo, const std::string &thread_name, const std::string &reason,
@@ -1652,7 +1657,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
 
     if (!thread_sp) {
       // Create the thread if we need to
-      thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
+      thread_sp = CreateThread(tid);
       m_thread_list_real.AddThread(thread_sp);
     }
   }
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index c1ea1cc7905587..0463e39b7a63a4 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -355,6 +355,8 @@ class ProcessGDBRemote : public Process,
   MonitorDebugserverProcess(std::weak_ptr<ProcessGDBRemote> process_wp,
                             lldb::pid_t pid, int signo, int exit_status);
 
+  virtual std::shared_ptr<ThreadGDBRemote> CreateThread(lldb::tid_t tid);
+
   lldb::StateType SetThreadStopInfo(StringExtractor &stop_packet);
 
   bool
diff --git a/lldb/source/Plugins/Process/wasm/CMakeLists.txt b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
new file mode 100644
index 00000000000000..ef2bfc634962d5
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_lldb_library(lldbPluginProcessWasm PLUGIN
+  ProcessWasm.cpp
+  ThreadWasm.cpp
+  UnwindWasm.cpp
+  wasmRegisterContext.cpp
+
+  LINK_LIBS
+    lldbCore
+    ${LLDB_PLUGINS}
+  LINK_COMPONENTS
+    Support
+  )
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
new file mode 100644
index 00000000000000..6e633e69b9767a
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -0,0 +1,291 @@
+//===-- ProcessWasm.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ProcessWasm.h"
+#include "ThreadWasm.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Utility/DataBufferHeap.h"
+
+#include "lldb/Target/UnixSignals.h"
+#include "llvm/ADT/ArrayRef.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+using namespace lldb_private::wasm;
+
+LLDB_PLUGIN_DEFINE(ProcessWasm)
+
+// ProcessGDBRemote constructor
+ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
+    : ProcessGDBRemote(target_sp, listener_sp) {
+  /* always use linux signals for wasm process */
+  m_unix_signals_sp =
+      UnixSignals::Create(ArchSpec{"wasm32-unknown-unknown-wasm"});
+}
+
+void ProcessWasm::Initialize() {
+  static llvm::once_flag g_once_flag;
+
+  llvm::call_once(g_once_flag, []() {
+    PluginManager::RegisterPlugin(GetPluginNameStatic(),
+                                  GetPluginDescriptionStatic(), CreateInstance,
+                                  DebuggerInitialize);
+  });
+}
+
+void ProcessWasm::DebuggerInitialize(Debugger &debugger) {
+  ProcessGDBRemote::DebuggerInitialize(debugger);
+}
+
+llvm::StringRef ProcessWasm::GetPluginName() { return GetPluginNameStatic(); }
+
+llvm::StringRef ProcessWasm::GetPluginNameStatic() {
+  static ConstString g_name("wasm");
+  return g_name;
+}
+
+llvm::StringRef ProcessWasm::GetPluginDescriptionStatic() {
+  return "GDB Remote protocol based WebAssembly debugging plug-in.";
+}
+
+void ProcessWasm::Terminate() {
+  PluginManager::UnregisterPlugin(ProcessWasm::CreateInstance);
+}
+
+lldb::ProcessSP ProcessWasm::CreateInstance(lldb::TargetSP target_sp,
+                                            ListenerSP listener_sp,
+                                            const FileSpec *crash_file_path,
+                                            bool can_connect) {
+  lldb::ProcessSP process_sp;
+  if (crash_file_path == nullptr)
+    process_sp = std::make_shared<ProcessWasm>(target_sp, listener_sp);
+  return process_sp;
+}
+
+bool ProcessWasm::CanDebug(lldb::TargetSP target_sp,
+                           bool plugin_specified_by_name) {
+  if (plugin_specified_by_name)
+    return true;
+
+  Module *exe_module = target_sp->GetExecutableModulePointer();
+  if (exe_module) {
+    ObjectFile *exe_objfile = exe_module->GetObjectFile();
+    return exe_objfile->GetArchitecture().GetMachine() == llvm::Triple::wasm32;
+  }
+  // However, if there is no wasm module, we return false, otherwise,
+  // we might use ProcessWasm to attach gdb remote.
+  return false;
+}
+
+std::shared_ptr<ThreadGDBRemote> ProcessWasm::CreateThread(lldb::tid_t tid) {
+  return std::make_shared<ThreadWasm>(*this, tid);
+}
+
+size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                               Status &error) {
+  wasm_addr_t wasm_addr(vm_addr);
+
+  switch (wasm_addr.GetType()) {
+  case WasmAddressType::Memory:
+  case WasmAddressType::Object:
+    return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+  case WasmAddressType::Invalid:
+  default:
+    error.SetErrorStringWithFormat(
+        "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
+    return 0;
+  }
+}
+
+size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                               ExecutionContext *exe_ctx, Status &error) {
+  wasm_addr_t wasm_addr(vm_addr);
+
+  switch (wasm_addr.GetType()) {
+  case WasmAddressType::Memory: {
+    // If we don't have a valid module_id, this is actually a read from the
+    // Wasm memory space. We can calculate the module_id from the execution
+    // context.
+    if (wasm_addr.module_id == 0 && exe_ctx != nullptr) {
+      StackFrame *frame = exe_ctx->GetFramePtr();
+      assert(frame->CalculateTarget()->GetArchitecture().GetMachine() ==
+             llvm::Triple::wasm32);
+      wasm_addr.module_id = wasm_addr_t(frame->GetStackID().GetPC()).module_id;
+      wasm_addr.type = WasmAddressType::Memory;
+    }
+    if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size))
+      return size;
+    error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
+                                   vm_addr);
+    return 0;
+  }
+  case WasmAddressType::Object:
+    return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+  case WasmAddressType::Invalid:
+  default:
+    error.SetErrorStringWithFormat(
+        "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
+    return 0;
+  }
+}
+
+size_t ProcessWasm::WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr,
+                                   void *buf, size_t buffer_size) {
+  char packet[64];
+  int packet_len =
+      ::snprintf(packet, sizeof(packet), "qWasmMem:%d;%" PRIx64 ";%" PRIx64,
+                 wasm_module_id, static_cast<uint64_t>(addr),
+                 static_cast<uint64_t>(buffer_size));
+  assert(packet_len + 1 < (int)sizeof(packet));
+  UNUSED_IF_ASSERT_DISABLED(packet_len);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response,
+                                              GetInterruptTimeout()) ==
+      GDBRemoteCommunication::PacketResult::Success) {
+    if (response.IsNormalResponse()) {
+      return response.GetHexBytes(llvm::MutableArrayRef<uint8_t>(
+                                      static_cast<uint8_t *>(buf), buffer_size),
+                                  '\xdd');
+    }
+  }
+  return 0;
+}
+
+size_t ProcessWasm::WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr,
+                                 void *buf, size_t buffer_size) {
+  char packet[64];
+  int packet_len =
+      ::snprintf(packet, sizeof(packet), "qWasmData:%d;%" PRIx64 ";%" PRIx64,
+                 wasm_module_id, static_cast<uint64_t>(addr),
+                 static_cast<uint64_t>(buffer_size));
+  assert(packet_len + 1 < (int)sizeof(packet));
+  UNUSED_IF_ASSERT_DISABLED(packet_len);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response,
+                                              GetInterruptTimeout()) ==
+      GDBRemoteCommunication::PacketResult::Success) {
+    if (response.IsNormalResponse()) {
+      return response.GetHexBytes(llvm::MutableArrayRef<uint8_t>(
+                                      static_cast<uint8_t *>(buf), buffer_size),
+                                  '\xdd');
+    }
+  }
+  return 0;
+}
+
+bool ProcessWasm::GetWasmLocal(int frame_index, int index, void *buf,
+                               size_t buffer_size, size_t &size) {
+  StreamString packet;
+  packet.Printf("qWasmLocal:");
+  packet.Printf("%d;%d", frame_index, index);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      GDBRemoteCommunication::PacketResult::Success) {
+    return false;
+  }
+
+  if (!response.IsNormalResponse()) {
+    return false;
+  }
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  size = buffer_sp->GetByteSize();
+  if (size <= buffer_size) {
+    memcpy(buf, buffer_sp->GetBytes(), size);
+    return true;
+  }
+
+  return false;
+}
+
+bool ProcessWasm::GetWasmGlobal(int frame_index, int index, void *buf,
+                                size_t buffer_size, size_t &size) {
+  StreamString packet;
+  packet.PutCString("qWasmGlobal:");
+  packet.Printf("%d;%d", frame_index, index);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      GDBRemoteCommunication::PacketResult::Success) {
+    return false;
+  }
+
+  if (!response.IsNormalResponse()) {
+    return false;
+  }
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  size = buffer_sp->GetByteSize();
+  if (size <= buffer_size) {
+    memcpy(buf, buffer_sp->GetBytes(), size);
+    return true;
+  }
+
+  return false;
+}
+
+bool ProcessWasm::GetWasmStackValue(int frame_index, int index, void *buf,
+                                    size_t buffer_size, size_t &size) {
+  StreamString packet;
+  packet.PutCString("qWasmStackValue:");
+  packet.Printf("%d;%d", frame_index, index);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      GDBRemoteCommunication::PacketResult::Success) {
+    return false;
+  }
+
+  if (!response.IsNormalResponse()) {
+    return false;
+  }
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  size = buffer_sp->GetByteSize();
+  if (size <= buffer_size) {
+    memcpy(buf, buffer_sp->GetBytes(), size);
+    return true;
+  }
+
+  return false;
+}
+
+bool ProcessWasm::GetWasmCallStack(lldb::tid_t tid,
+                                   std::vector<lldb::addr_t> &call_stack_pcs) {
+  call_stack_pcs.clear();
+  StreamString packet;
+  packet.Printf("qWasmCallStack:");
+  packet.Printf("%llx", tid);
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      GDBRemoteCommunication::PacketResult::Success) {
+    return false;
+  }
+
+  if (!response.IsNormalResponse()) {
+    return false;
+  }
+
+  addr_t buf[1024 / sizeof(addr_t)];
+  size_t bytes = response.GetHexBytes(
+      llvm::MutableArrayRef<uint8_t>((uint8_t *)buf, sizeof(buf)), '\xdd');
+  if (bytes == 0) {
+    return false;
+  }
+
+  for (size_t i = 0; i < bytes / sizeof(addr_t); i++) {
+    call_stack_pcs.push_back(buf[i]);
+  }
+  return true;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
new file mode 100644
index 00000000000000..fc9736ec0684d2
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -0,0 +1,129 @@
+//===-- ProcessWasm.h -------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
+
+#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
+#include "lldb/Target/RegisterContext.h"
+
+namespace lldb_private {
+namespace wasm {
+
+/// Each WebAssembly module has separated address spaces for Code and Memory.
+/// A WebAssembly module also has a Data section which, when the module is
+/// loaded, gets mapped into a region in the module Memory.
+/// For the purpose of debugging, we can represent all these separated 32-bit
+/// address spaces with a single virtual 64-bit address space.
+///
+/// Struct wasm_addr_t provides this encoding using bitfields
+///
+enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0x03 };
+
+struct wasm_addr_t {
+  uint64_t offset : 32;
+  uint64_t module_id : 30;
+  uint64_t type : 2;
+
+  wasm_addr_t(lldb::addr_t addr)
+      : type(addr >> 62), module_id((addr & 0x00ffffff00000000) >> 32),
+        offset(addr & 0x00000000ffffffff) {}
+
+  wasm_addr_t(WasmAddressType type_, uint32_t module_id_, uint32_t offset_)
+      : type(type_), module_id(module_id_), offset(offset_) {}
+
+  WasmAddressType GetType() { return static_cast<WasmAddressType>(type); }
+  operator lldb::addr_t() { return *(uint64_t *)this; }
+};
+
+/// ProcessWasm provides the access to the Wasm program state
+/// retrieved from the Wasm engine.
+class ProcessWasm : public process_gdb_remote::ProcessGDBRemote {
+public:
+  ProcessWasm(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
+  ~ProcessWasm() override = default;
+
+  static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
+                                        lldb::ListenerSP listener_sp,
+                                        const FileSpec *crash_file_path,
+                                        bool can_connect);
+
+  static void Initialize();
+  static void DebuggerInitialize(Debugger &debugger);
+  static void Terminate();
+  static llvm::StringRef GetPluginNameStatic();
+  static llvm::StringRef GetPluginDescriptionStatic();
+
+  /// PluginInterface protocol.
+  /// \{
+  llvm::StringRef GetPluginName() override;
+  /// \}
+
+  /// Process protocol.
+  /// \{
+  size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                    Status &error) override;
+
+  size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                    ExecutionContext *exe_ctx, Status &error) override;
+  /// \}
+
+  /// Query the value of a WebAssembly local variable from the WebAssembly
+  /// remote process.
+  bool GetWasmLocal(int frame_index, int index, void *buf, size_t buffer_size,
+                    size_t &size);
+
+  /// Query the value of a WebAssembly global variable from the WebAssembly
+  /// remote process.
+  bool GetWasmGlobal(int frame_index, int index, void *buf, size_t buffer_size,
+                     size_t &size);
+
+  /// Query the value of an item in the WebAssembly operand stack from the
+  /// WebAssembly remote process.
+  bool GetWasmStackValue(int frame_index, int index, void *buf,
+                         size_t buffer_size, size_t &size);
+
+  /// Read from the WebAssembly Memory space.
+  size_t WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr, void *buf,
+                        size_t buffer_size);
+
+  /// Read from the WebAssembly Data space.
+  size_t WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr, void *buf,
+                      size_t buffer_size);
+
+  /// Retrieve the current call stack from the WebAssembly remote process.
+  bool GetWasmCallStack(lldb::tid_t tid,
+                        std::vector<lldb::addr_t> &call_stack_pcs);
+
+  // Check if a given Process
+  bool CanDebug(lldb::TargetSP target_sp,
+                bool plugin_specified_by_name) override;
+
+protected:
+  /// ProcessGDBRemote protocol.
+  /// \{
+  std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
+  CreateThread(lldb::tid_t tid);
+  /// \}
+
+private:
+  friend class UnwindWasm;
+  friend class ThreadWasm;
+
+  process_gdb_remote::GDBRemoteDynamicRegisterInfoSP &GetRegisterInfo() {
+    return m_register_info_sp;
+  }
+
+  ProcessWasm(const ProcessWasm &);
+  const ProcessWasm &operator=(const ProcessWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
new file mode 100644
index 00000000000000..9d3ee48912e405
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
@@ -0,0 +1,55 @@
+//===-- ThreadWasm.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ThreadWasm.h"
+
+#include "ProcessWasm.h"
+#include "UnwindWasm.h"
+#include "lldb/Target/Target.h"
+#include "wasmRegisterContext.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::wasm;
+
+Unwind &ThreadWasm::GetUnwinder() {
+  if (!m_unwinder_up) {
+    assert(CalculateTarget()->GetArchitecture().GetMachine() ==
+           llvm::Triple::wasm32);
+    m_unwinder_up.reset(new wasm::UnwindWasm(*this));
+  }
+  return *m_unwinder_up;
+}
+
+bool ThreadWasm::GetWasmCallStack(std::vector<lldb::addr_t> &call_stack_pcs) {
+  ProcessSP process_sp(GetProcess());
+  if (process_sp) {
+    ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+    return wasm_process->GetWasmCallStack(GetID(), call_stack_pcs);
+  }
+  return false;
+}
+
+lldb::RegisterContextSP
+ThreadWasm::CreateRegisterContextForFrame(StackFrame *frame) {
+  lldb::RegisterContextSP reg_ctx_sp;
+  uint32_t concrete_frame_idx = 0;
+  ProcessSP process_sp(GetProcess());
+  ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+
+  if (frame)
+    concrete_frame_idx = frame->GetConcreteFrameIndex();
+
+  if (concrete_frame_idx == 0) {
+    reg_ctx_sp = std::make_shared<WasmRegisterContext>(
+        *this, concrete_frame_idx, wasm_process->GetRegisterInfo());
+  } else {
+    reg_ctx_sp = GetUnwinder().CreateRegisterContextForFrame(frame);
+  }
+  return reg_ctx_sp;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.h b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
new file mode 100644
index 00000000000000..c1aa2cd8b98825
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
@@ -0,0 +1,44 @@
+//===-- ThreadWasm.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
+
+#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h"
+
+namespace lldb_private {
+namespace wasm {
+
+/// ProcessWasm provides the access to the Wasm program state
+///  retrieved from the Wasm engine.
+class ThreadWasm : public process_gdb_remote::ThreadGDBRemote {
+public:
+  ThreadWasm(Process &process, lldb::tid_t tid)
+      : process_gdb_remote::ThreadGDBRemote(process, tid) {}
+  ~ThreadWasm() override = default;
+
+  /// Retrieve the current call stack from the WebAssembly remote process.
+  bool GetWasmCallStack(std::vector<lldb::addr_t> &call_stack_pcs);
+
+  lldb::RegisterContextSP
+  CreateRegisterContextForFrame(StackFrame *frame) override;
+
+protected:
+  /// Thread protocol.
+  /// \{
+  Unwind &GetUnwinder() override;
+  /// \}
+
+  ThreadWasm(const ThreadWasm &);
+  const ThreadWasm &operator=(const ThreadWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
new file mode 100644
index 00000000000000..9e8f6a9ce7d9ff
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
@@ -0,0 +1,76 @@
+//===-- UnwindWasm.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UnwindWasm.h"
+#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h"
+#include "Plugins/Process/wasm/ProcessWasm.h"
+#include "Plugins/Process/wasm/ThreadWasm.h"
+#include "lldb/lldb-forward.h"
+#include "wasmRegisterContext.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace process_gdb_remote;
+using namespace wasm;
+
+class WasmGDBRemoteRegisterContext : public GDBRemoteRegisterContext {
+public:
+  WasmGDBRemoteRegisterContext(ThreadGDBRemote &thread,
+                               uint32_t concrete_frame_idx,
+                               GDBRemoteDynamicRegisterInfoSP &reg_info_sp,
+                               uint64_t pc)
+      : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
+                                 false) {
+    PrivateSetRegisterValue(0, pc);
+  }
+};
+
+lldb::RegisterContextSP
+UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) {
+  if (m_frames.size() <= frame->GetFrameIndex()) {
+    return lldb::RegisterContextSP();
+  }
+
+  ThreadSP thread = frame->GetThread();
+  ProcessSP process_sp = thread->GetProcess();
+  ThreadWasm *wasm_thread = static_cast<ThreadWasm *>(thread.get());
+  ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+  std::shared_ptr<WasmRegisterContext> reg_ctx_sp =
+      std::make_shared<WasmRegisterContext>(*wasm_thread,
+                                            frame->GetConcreteFrameIndex(),
+                                            wasm_process->GetRegisterInfo());
+  return reg_ctx_sp;
+}
+
+uint32_t UnwindWasm::DoGetFrameCount() {
+  if (!m_unwind_complete) {
+    m_unwind_complete = true;
+    m_frames.clear();
+
+    ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(GetThread());
+    if (!wasm_thread.GetWasmCallStack(m_frames))
+      m_frames.clear();
+  }
+  return m_frames.size();
+}
+
+bool UnwindWasm::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
+                                       lldb::addr_t &pc,
+                                       bool &behaves_like_zeroth_frame) {
+  if (m_frames.size() == 0) {
+    DoGetFrameCount();
+  }
+
+  if (frame_idx < m_frames.size()) {
+    behaves_like_zeroth_frame = (frame_idx == 0);
+    cfa = 0;
+    pc = m_frames[frame_idx];
+    return true;
+  }
+  return false;
+}
\ No newline at end of file
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.h b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
new file mode 100644
index 00000000000000..9bd1dac9a98ae3
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
@@ -0,0 +1,55 @@
+//===-- UnwindWasm.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_UnwindWasm_h_
+#define lldb_UnwindWasm_h_
+
+#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/Unwind.h"
+#include <vector>
+
+namespace lldb_private {
+namespace wasm {
+
+/// UnwindWasm manages stack unwinding for a WebAssembly process.
+class UnwindWasm : public lldb_private::Unwind {
+public:
+  UnwindWasm(lldb_private::Thread &thread)
+      : Unwind(thread), m_frames(), m_unwind_complete(false) {}
+  ~UnwindWasm() override = default;
+
+protected:
+  /// Unwind protocol.
+  /// \{
+  void DoClear() override {
+    m_frames.clear();
+    m_unwind_complete = false;
+  }
+
+  uint32_t DoGetFrameCount() override;
+
+  bool DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
+                             lldb::addr_t &pc,
+                             bool &behaves_like_zeroth_frame) override;
+
+  lldb::RegisterContextSP
+  DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) override;
+  /// \}
+
+private:
+  std::vector<lldb::addr_t> m_frames;
+  bool m_unwind_complete;
+
+  UnwindWasm(const UnwindWasm &);
+  const UnwindWasm &operator=(const UnwindWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // lldb_UnwindWasm_h_
diff --git a/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
new file mode 100644
index 00000000000000..470a0fb6a43cb3
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
@@ -0,0 +1,108 @@
+//===---- wasmRegisterContext.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "wasmRegisterContext.h"
+#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
+#include "ProcessWasm.h"
+#include "ThreadWasm.h"
+#include "lldb/Utility/RegisterValue.h"
+#include <memory>
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+using namespace lldb_private::wasm;
+
+WasmRegisterContext::WasmRegisterContext(
+    wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
+    GDBRemoteDynamicRegisterInfoSP reg_info_sp)
+    : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
+                               false) {}
+
+WasmRegisterContext::~WasmRegisterContext() = default;
+
+uint32_t WasmRegisterContext::ConvertRegisterKindToRegisterNumber(
+    lldb::RegisterKind kind, uint32_t num) {
+  return num;
+}
+
+size_t WasmRegisterContext::GetRegisterCount() { return 0; }
+
+const RegisterInfo *WasmRegisterContext::GetRegisterInfoAtIndex(size_t reg) {
+  uint32_t tag = (reg >> 30) & 0x03;
+  if (tag == 0) {
+    return m_reg_info_sp->GetRegisterInfoAtIndex(reg);
+  }
+
+  auto it = m_register_map.find(reg);
+  if (it == m_register_map.end()) {
+    WasmVirtualRegisterKinds kind =
+        static_cast<WasmVirtualRegisterKinds>(tag - 1);
+    std::tie(it, std::ignore) = m_register_map.insert(
+        {reg,
+         std::make_unique<WasmVirtualRegisterInfo>(kind, reg & 0x3fffffff)});
+  }
+  return it->second.get();
+}
+
+size_t WasmRegisterContext::GetRegisterSetCount() { return 0; }
+
+const RegisterSet *WasmRegisterContext::GetRegisterSet(size_t reg_set) {
+  return nullptr;
+}
+
+bool WasmRegisterContext::ReadRegister(const RegisterInfo *reg_info,
+                                       RegisterValue &value) {
+  if (reg_info->name) {
+    return GDBRemoteRegisterContext::ReadRegister(reg_info, value);
+  }
+
+  ThreadWasm *thread = static_cast<ThreadWasm *>(&GetThread());
+  ProcessWasm *process = static_cast<ProcessWasm *>(thread->GetProcess().get());
+  if (!thread)
+    return false;
+
+  uint32_t frame_index = m_concrete_frame_idx;
+  WasmVirtualRegisterInfo *wasm_reg_info =
+      static_cast<WasmVirtualRegisterInfo *>(
+          const_cast<RegisterInfo *>(reg_info));
+  uint8_t buf[16];
+  size_t size = 0;
+  switch (wasm_reg_info->kind) {
+  case eLocal:
+    process->GetWasmLocal(frame_index, wasm_reg_info->index, buf, sizeof(buf),
+                          size);
+    break;
+  case eGlobal:
+    process->GetWasmGlobal(frame_index, wasm_reg_info->index, buf, sizeof(buf),
+                           size);
+    break;
+  case eOperandStack:
+    process->GetWasmStackValue(frame_index, wasm_reg_info->index, buf,
+                               sizeof(buf), size);
+    break;
+  default:
+    return false;
+  }
+
+  DataExtractor reg_data(buf, size, process->GetByteOrder(),
+                         process->GetAddressByteSize());
+  const bool partial_data_ok = false;
+  wasm_reg_info->byte_size = size;
+  wasm_reg_info->encoding = lldb::eEncodingUint;
+  Status error(value.SetValueFromData(*reg_info, reg_data,
+                                      reg_info->byte_offset, partial_data_ok));
+  return error.Success();
+}
+
+void WasmRegisterContext::InvalidateAllRegisters() {}
+
+bool WasmRegisterContext::WriteRegister(const RegisterInfo *reg_info,
+                                        const RegisterValue &value) {
+  return false;
+}
diff --git a/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h
new file mode 100644
index 00000000000000..f60cc8e6a099ab
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h
@@ -0,0 +1,75 @@
+//===----- wasmRegisterContext.h --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H
+
+#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
+#include "ThreadWasm.h"
+#include "lldb/lldb-private-types.h"
+#include <unordered_map>
+
+namespace lldb_private {
+namespace wasm {
+
+class WasmRegisterContext;
+
+typedef std::shared_ptr<WasmRegisterContext> WasmRegisterContextSP;
+
+enum WasmVirtualRegisterKinds {
+  eLocal = 0,    ///< wasm local
+  eGlobal,       ///< wasm global
+  eOperandStack, ///< wasm operand stack
+  kNumWasmVirtualRegisterKinds
+};
+
+struct WasmVirtualRegisterInfo : public RegisterInfo {
+  WasmVirtualRegisterKinds kind;
+  uint32_t index;
+
+  WasmVirtualRegisterInfo(WasmVirtualRegisterKinds kind, uint32_t index)
+      : RegisterInfo(), kind(kind), index(index) {}
+};
+
+class WasmRegisterContext
+    : public process_gdb_remote::GDBRemoteRegisterContext {
+public:
+  WasmRegisterContext(
+      wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
+      process_gdb_remote::GDBRemoteDynamicRegisterInfoSP reg_info_sp);
+
+  ~WasmRegisterContext() override;
+
+  uint32_t ConvertRegisterKindToRegisterNumber(lldb::RegisterKind kind,
+                                               uint32_t num) override;
+
+  void InvalidateAllRegisters() override;
+
+  size_t GetRegisterCount() override;
+
+  const RegisterInfo *GetRegisterInfoAtIndex(size_t reg) override;
+
+  size_t GetRegisterSetCount() override;
+
+  const RegisterSet *GetRegisterSet(size_t reg_set) override;
+
+  bool ReadRegister(const RegisterInfo *reg_info,
+                    RegisterValue &value) override;
+
+  bool WriteRegister(const RegisterInfo *reg_info,
+                     const RegisterValue &value) override;
+
+private:
+  std::unordered_map<size_t, std::unique_ptr<WasmVirtualRegisterInfo>>
+      m_register_map;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H

>From cbd046618749f229fce7221e012bf51f4789301b Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Mon, 22 Jan 2024 05:17:23 -0800
Subject: [PATCH 2/3] Add unit tests

---
 lldb/include/lldb/Target/Process.h            |  54 +---
 lldb/source/Core/Value.cpp                    |   5 +-
 lldb/source/Expression/DWARFExpression.cpp    |   2 +-
 lldb/source/Expression/Materializer.cpp       |   4 +-
 .../Plugins/Process/wasm/ProcessWasm.cpp      |  62 ++---
 .../source/Plugins/Process/wasm/ProcessWasm.h |  13 +-
 .../gdb_remote_client/TestWasm.py             | 239 ++++++++++++++----
 7 files changed, 242 insertions(+), 137 deletions(-)

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 587ae085b479b7..b7779985db4fc3 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1449,6 +1449,14 @@ class Process : public std::enable_shared_from_this<Process>,
   /// platforms where there is a difference (only Arm Thumb at this time).
   lldb::addr_t FixAnyAddress(lldb::addr_t pc);
 
+  /// Some targets might use bits in a code address to represent additional
+  /// information; for example WebAssembly targets have a different memory space
+  /// per module and have a different address space per memory and code.
+  virtual lldb::addr_t FixMemoryAddress(lldb::addr_t address,
+                                        StackFrame *stack_frame) const {
+    return address;
+  }
+
   /// Get the Modification ID of the process.
   ///
   /// \return
@@ -1548,46 +1556,6 @@ class Process : public std::enable_shared_from_this<Process>,
   virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                             Status &error);
 
-  /// Read of memory from a process.
-  ///
-  /// This function will read memory from the current process's address space
-  /// and remove any traps that may have been inserted into the memory.
-  ///
-  /// This overloads accepts an ExecutionContext as additional argument. By
-  /// default, it calls the previous overload without the ExecutionContext
-  /// argument, but it can be overridden by Process subclasses.
-  ///
-  /// \param[in] vm_addr
-  ///     A virtual load address that indicates where to start reading
-  ///     memory from.
-  ///
-  /// \param[out] buf
-  ///     A byte buffer that is at least \a size bytes long that
-  ///     will receive the memory bytes.
-  ///
-  /// \param[in] size
-  ///     The number of bytes to read.
-  ///
-  /// \param[in] exe_ctx
-  ///    The current execution context, if available.
-  ///
-  /// \param[out] error
-  ///     An error that indicates the success or failure of this
-  ///     operation. If error indicates success (error.Success()),
-  ///     then the value returned can be trusted, otherwise zero
-  ///     will be returned.
-  ///
-  /// \return
-  ///     The number of bytes that were actually read into \a buf. If
-  ///     the returned number is greater than zero, yet less than \a
-  ///     size, then this function will get called again with \a
-  ///     vm_addr, \a buf, and \a size updated appropriately. Zero is
-  ///     returned in the case of an error.
-  virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
-                            ExecutionContext *exe_ctx, Status &error) {
-    return ReadMemory(vm_addr, buf, size, error);
-  }
-
   /// Read of memory from a process.
   ///
   /// This function has the same semantics of ReadMemory except that it
@@ -1965,9 +1933,9 @@ class Process : public std::enable_shared_from_this<Process>,
   ///     the instruction has completed executing.
   bool GetWatchpointReportedAfter();
 
-  lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
-                                      lldb::addr_t header_addr,
-                                      size_t size_to_read = 512);
+  virtual lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
+                                              lldb::addr_t header_addr,
+                                              size_t size_to_read = 512);
 
   /// Attempt to get the attributes for a region of memory in the process.
   ///
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 47a5fdee773886..7766d6543edb0c 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -551,8 +551,9 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
         Process *process = exe_ctx->GetProcessPtr();
 
         if (process) {
-          const size_t bytes_read =
-              process->ReadMemory(address, dst, byte_size, exe_ctx, error);
+          const size_t bytes_read = process->ReadMemory(
+              process->FixMemoryAddress(address, exe_ctx->GetFramePtr()), dst,
+              byte_size, error);
           if (bytes_read != byte_size)
             error.SetErrorStringWithFormat(
                 "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index ca24611724dc7c..95033db5ed8f5a 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -2621,7 +2621,7 @@ bool DWARFExpression::Evaluate(
        */
       if (wasm_op == 3) {
         index = opcodes.GetU32(&offset);
-        wasm_op = 1;
+        wasm_op = 2; // Global
       } else {
         index = opcodes.GetULEB128(&offset);
       }
diff --git a/lldb/source/Expression/Materializer.cpp b/lldb/source/Expression/Materializer.cpp
index 6e344dfd57c4b5..8c4cd4dfc2db5f 100644
--- a/lldb/source/Expression/Materializer.cpp
+++ b/lldb/source/Expression/Materializer.cpp
@@ -1077,7 +1077,9 @@ class EntityResultVariable : public Materializer::Entity {
     const size_t pvar_byte_size = ret->GetByteSize().value_or(0);
     uint8_t *pvar_data = ret->GetValueBytes();
 
-    map.ReadMemory(pvar_data, address, pvar_byte_size, read_error);
+    map.ReadMemory(pvar_data,
+                   process_sp->FixMemoryAddress(address, frame_sp.get()),
+                   pvar_byte_size, read_error);
 
     if (!read_error.Success()) {
       err.SetErrorString(
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index 6e633e69b9767a..19b8babeb59351 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -94,8 +94,19 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
 
   switch (wasm_addr.GetType()) {
   case WasmAddressType::Memory:
-  case WasmAddressType::Object:
-    return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+    if (wasm_addr.module_id != 0) {
+      if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size)) {
+        return size;
+      }
+      error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
+                                     vm_addr);
+      return 0;
+    } else {
+      return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+    }
+  case WasmAddressType::Code:
+    wasm_addr.type = 0;
+    return ProcessGDBRemote::ReadMemory(wasm_addr, buf, size, error);
   case WasmAddressType::Invalid:
   default:
     error.SetErrorStringWithFormat(
@@ -104,36 +115,27 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
   }
 }
 
-size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
-                               ExecutionContext *exe_ctx, Status &error) {
-  wasm_addr_t wasm_addr(vm_addr);
+lldb::ModuleSP ProcessWasm::ReadModuleFromMemory(const FileSpec &file_spec,
+                                                 lldb::addr_t header_addr,
+                                                 size_t size_to_read) {
+  wasm_addr_t wasm_addr(header_addr);
+  wasm_addr.type = WasmAddressType::Code;
+  return Process::ReadModuleFromMemory(file_spec, wasm_addr, size_to_read);
+}
 
-  switch (wasm_addr.GetType()) {
-  case WasmAddressType::Memory: {
-    // If we don't have a valid module_id, this is actually a read from the
-    // Wasm memory space. We can calculate the module_id from the execution
-    // context.
-    if (wasm_addr.module_id == 0 && exe_ctx != nullptr) {
-      StackFrame *frame = exe_ctx->GetFramePtr();
-      assert(frame->CalculateTarget()->GetArchitecture().GetMachine() ==
-             llvm::Triple::wasm32);
-      wasm_addr.module_id = wasm_addr_t(frame->GetStackID().GetPC()).module_id;
-      wasm_addr.type = WasmAddressType::Memory;
-    }
-    if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size))
-      return size;
-    error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
-                                   vm_addr);
-    return 0;
-  }
-  case WasmAddressType::Object:
-    return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
-  case WasmAddressType::Invalid:
-  default:
-    error.SetErrorStringWithFormat(
-        "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
-    return 0;
+lldb::addr_t ProcessWasm::FixMemoryAddress(lldb::addr_t address,
+                                           StackFrame *stack_frame) const {
+  if (stack_frame) {
+    assert(stack_frame->CalculateTarget()->GetArchitecture().GetMachine() ==
+           llvm::Triple::wasm32);
+    // Extract Wasm module ID from the program counter.
+    wasm_addr_t wasm_addr(address);
+    wasm_addr.module_id =
+        wasm_addr_t(stack_frame->GetStackID().GetPC()).module_id;
+    wasm_addr.type = WasmAddressType::Memory;
+    return wasm_addr;
   }
+  return address;
 }
 
 size_t ProcessWasm::WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr,
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index fc9736ec0684d2..b1051b0840c2b2 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -23,7 +23,7 @@ namespace wasm {
 ///
 /// Struct wasm_addr_t provides this encoding using bitfields
 ///
-enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0x03 };
+enum WasmAddressType { Memory = 0x00, Code = 0x01, Invalid = 0x03 };
 
 struct wasm_addr_t {
   uint64_t offset : 32;
@@ -64,14 +64,15 @@ class ProcessWasm : public process_gdb_remote::ProcessGDBRemote {
   llvm::StringRef GetPluginName() override;
   /// \}
 
-  /// Process protocol.
-  /// \{
   size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                     Status &error) override;
 
-  size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
-                    ExecutionContext *exe_ctx, Status &error) override;
-  /// \}
+  lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
+                                      lldb::addr_t header_addr,
+                                      size_t size_to_read = 512) override;
+
+  lldb::addr_t FixMemoryAddress(lldb::addr_t address,
+                                StackFrame *stack_frame) const override;
 
   /// Query the value of a WebAssembly local variable from the WebAssembly
   /// remote process.
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index 61f315bb65ca53..f2ac2abdeef0c0 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -6,8 +6,8 @@
 from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
 
 LLDB_INVALID_ADDRESS = lldb.LLDB_INVALID_ADDRESS
-load_address = 0x400000000
-
+MODULE_ID = 4
+LOAD_ADDRESS = MODULE_ID << 32
 
 def format_register_value(val):
     """
@@ -23,18 +23,33 @@ def format_register_value(val):
         shift += 8
     return result
 
+def make_code_address(module_id, offset):
+    return 0x4000000000000000 | (module_id << 32) | offset
 
 class MyResponder(MockGDBServerResponder):
-    current_pc = load_address + 0x0A
+    current_pc = LOAD_ADDRESS + 0x0A
 
-    def __init__(self, obj_path, module_name=""):
+    def __init__(self, obj_path, module_name, wasm_call_stacks=[], memory_view=[]):
         self._obj_path = obj_path
         self._module_name = module_name or obj_path
+        self._bp_address = 0
+        self._wasm_call_stacks = wasm_call_stacks
+        self._call_stack_request_count = 0
+        self._memory_view = memory_view
         MockGDBServerResponder.__init__(self)
 
+    def SetCurrentPC(self, address):
+        self.current_pc = LOAD_ADDRESS + address
+
     def respond(self, packet):
         if packet[0:13] == "qRegisterInfo":
             return self.qRegisterInfo(packet[13:])
+        if packet.startswith("qWasmCallStack"):
+            return self.qWasmCallStack();
+        if packet.startswith("qWasmLocal"):
+            return self.qWasmLocal(packet);
+        if packet.startswith("qWasmMem"):
+            return self.qWasmMem(packet);
         return MockGDBServerResponder.respond(self, packet)
 
     def qSupported(self, client_supported):
@@ -50,7 +65,7 @@ def qfThreadInfo(self):
         return "OK"
 
     def qRegisterInfo(self, index):
-        if index == 0:
+        if index == "0":
             return "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"
         return "E45"
 
@@ -61,36 +76,131 @@ def qProcessInfo(self):
         )
 
     def haltReason(self):
-        return "T05thread:1;"
+        return "T05thread-pcs:300000083;thread:1;library:;"
 
     def readRegister(self, register):
+        assert(register == 0)
         return format_register_value(self.current_pc)
 
     def qXferRead(self, obj, annex, offset, length):
         if obj == "libraries":
             xml = (
                 '<library-list><library name="%s"><section address="%d"/></library></library-list>'
-                % (self._module_name, load_address)
+                % (self._module_name, make_code_address(MODULE_ID, 0))
             )
             return xml, False
         else:
             return None, False
 
     def readMemory(self, addr, length):
-        if addr < load_address:
-            return "E02"
         result = ""
         with open(self._obj_path, mode="rb") as file:
+            if addr < LOAD_ADDRESS:
+                return "E02"
             file_content = bytearray(file.read())
-            addr_from = addr - load_address
+            if addr >= LOAD_ADDRESS + len(file_content):
+                return "E03"
+            addr_from = addr - LOAD_ADDRESS
             addr_to = addr_from + min(length, len(file_content) - addr_from)
             for i in range(addr_from, addr_to):
                 result += format(file_content[i], "02x")
             file.close()
         return result
 
+    def setBreakpoint(self, packet):
+        bp_data = packet[1:].split(",")
+        self._bp_address = bp_data[1]
+        return "OK"
+
+    def qfThreadInfo(self):
+        return "m1"
+
+    def cont(self):
+        # Continue execution. Simulates running the Wasm engine until a breakpoint is hit.
+        return "T05thread-pcs:" + format(int(self._bp_address, 16) & 0x3fffffffffffffff, 'x') + ";thread:1"
+
+    def qWasmCallStack(self):
+        if len(self._wasm_call_stacks) == 0:
+            return ""
+        result = self._wasm_call_stacks[self._call_stack_request_count].format();
+        self._call_stack_request_count = self._call_stack_request_count + 1
+        return result
+
+    def qWasmLocal(self, packet):
+        # Format: qWasmLocal:frame_index;index
+        data = packet.split(":")
+        data = data[1].split(";")
+        frame_index = data[0]
+        local_index = data[1]
+        if frame_index == '0' and local_index == '4':
+            return "b0ff0000"
+        if frame_index == '1' and local_index == '5':
+            return "c0ff0000"
+        return "E03";
+
+    def qWasmMem(self, packet):
+        # Format: qWasmMem:module_id;addr;len
+        data = packet.split(":")
+        data = data[1].split(";")
+        module_id = data[0]
+        addr = int(data[1], 16)
+        length = int(data[2])
+        if module_id != '4':
+            return "E03"
+        if addr >= 0xffb8 and addr < 0x10000:
+            chunk = self._memory_view[addr:addr+length]
+            return chunk.hex()
+        return "E03";
+
+class WasmStackFrame:
+    pass
+
+    def __init__(self, module_id, address):
+        self._module_id = module_id
+        self._address = address
+
+    def format(self):
+        return format_register_value(make_code_address(self._module_id, self._address))
+
+class WasmCallStack:
+    pass
+
+    def __init__(self, wasm_stack_frames):
+        self._wasm_stack_frames = wasm_stack_frames
+
+    def format(self):
+        result = ""
+        for frame in self._wasm_stack_frames:
+            result += frame.format()
+        return result
+
+class WasmMemorySpan:
+    pass
+
+    def __init__(self, offset, bytes):
+        self._offset = offset
+        self._bytes = bytes
 
 class TestWasm(GDBRemoteTestBase):
+    def connect_to_wasm_engine(self, target):
+        """
+        Create a process by connecting to the mock GDB server running in a mock WebAssembly engine.
+        Includes assertions that the process was successfully created.
+        """
+        listener = self.dbg.GetListener()
+        error = lldb.SBError()
+        process = target.ConnectRemote(
+            listener, self.server.get_connect_url(), "wasm", error
+        )
+        self.assertTrue(error.Success(), error.description)
+        self.assertTrue(process, PROCESS_IS_VALID)
+        return process
+
+    def store_bytes(self, offset, bytes_obj):
+        chunk = self.memory_view[offset:offset+len(bytes_obj)]
+        for i in range(len(bytes_obj)):
+            chunk[i] = bytes_obj[i]
+
     @skipIfAsan
     @skipIfXmlSupportMissing
     def test_load_module_with_embedded_symbols_from_remote(self):
@@ -104,7 +214,7 @@ def test_load_module_with_embedded_symbols_from_remote(self):
         self.server.responder = MyResponder(obj_path, "test_wasm")
 
         target = self.dbg.CreateTarget("")
-        process = self.connect(target)
+        process = self.connect_to_wasm_engine(target)
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
@@ -119,35 +229,35 @@ def test_load_module_with_embedded_symbols_from_remote(self):
         code_section = module.GetSectionAtIndex(0)
         self.assertEquals("code", code_section.GetName())
         self.assertEquals(
-            load_address | code_section.GetFileOffset(),
+            make_code_address(MODULE_ID, code_section.GetFileOffset()),
             code_section.GetLoadAddress(target),
         )
 
         debug_info_section = module.GetSectionAtIndex(1)
         self.assertEquals(".debug_info", debug_info_section.GetName())
         self.assertEquals(
-            load_address | debug_info_section.GetFileOffset(),
+            make_code_address(MODULE_ID, debug_info_section.GetFileOffset()),
             debug_info_section.GetLoadAddress(target),
         )
 
         debug_abbrev_section = module.GetSectionAtIndex(2)
         self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
         self.assertEquals(
-            load_address | debug_abbrev_section.GetFileOffset(),
+            make_code_address(MODULE_ID, debug_abbrev_section.GetFileOffset()),
             debug_abbrev_section.GetLoadAddress(target),
         )
 
         debug_line_section = module.GetSectionAtIndex(3)
         self.assertEquals(".debug_line", debug_line_section.GetName())
         self.assertEquals(
-            load_address | debug_line_section.GetFileOffset(),
+            make_code_address(MODULE_ID, debug_line_section.GetFileOffset()),
             debug_line_section.GetLoadAddress(target),
         )
 
         debug_str_section = module.GetSectionAtIndex(4)
         self.assertEquals(".debug_str", debug_str_section.GetName())
         self.assertEquals(
-            load_address | debug_line_section.GetFileOffset(),
+            make_code_address(MODULE_ID, debug_line_section.GetFileOffset()),
             debug_line_section.GetLoadAddress(target),
         )
 
@@ -174,7 +284,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
         )
 
         target = self.dbg.CreateTarget("")
-        process = self.connect(target)
+        process = self.connect_to_wasm_engine(target)
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
@@ -189,7 +299,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
         code_section = module.GetSectionAtIndex(0)
         self.assertEquals("code", code_section.GetName())
         self.assertEquals(
-            load_address | code_section.GetFileOffset(),
+            make_code_address(MODULE_ID, code_section.GetFileOffset()),
             code_section.GetLoadAddress(target),
         )
 
@@ -216,59 +326,80 @@ def test_load_module_with_stripped_symbols_from_remote(self):
         self.assertEquals(
             LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
         )
-
+    
     @skipIfAsan
     @skipIfXmlSupportMissing
-    def test_load_module_from_file(self):
-        """Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module from a file"""
+    def test_simple_wasm_debugging_session(self):
+        """Test connecting to a WebAssembly engine via GDB-remote, loading a Wasm module with embedded DWARF symbols, setting a breakpoint and checking the debuggee state"""
 
-        yaml_path = "test_wasm_embedded_debug_sections.yaml"
+        yaml_path = "calc-cpp-o0.yaml"
         yaml_base, ext = os.path.splitext(yaml_path)
-        obj_path = self.getBuildArtifact(yaml_base)
+        obj_path = self.getBuildArtifact(yaml_base) + ".wasm"
         self.yaml2obj(yaml_path, obj_path)
 
-        self.server.responder = MyResponder(obj_path)
+        self.memory = bytearray(65536)
+        self.memory_view = memoryview(self.memory)
+        self.store_bytes(0xffb8, bytes.fromhex("d8ff0000"))
+        self.store_bytes(0xffbc, bytes.fromhex("e0ff0000"))
+        self.store_bytes(0xffe0, bytes.fromhex("0000000000000000"))
+        self.store_bytes(0xffe8, bytes.fromhex("0000000000004540"))
+        self.store_bytes(0xfff0, bytes.fromhex("0000000000003640"))
+        self.store_bytes(0xfff8, bytes.fromhex("0000000000003440"))
+
+        call_stacks = [
+            WasmCallStack([WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)]),
+            WasmCallStack([WasmStackFrame(4, 0x000002ad), WasmStackFrame(4, 0x0000014a), WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)])
+        ]
+        self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks, self.memory_view)
 
         target = self.dbg.CreateTarget("")
-        process = self.connect(target)
+        breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9);
+
+        process = self.connect_to_wasm_engine(target)
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
 
-        num_modules = target.GetNumModules()
-        self.assertEquals(1, num_modules)
+        # Verify all breakpoint locations are enabled.
+        location = breakpoint.GetLocationAtIndex(0)
+        self.assertTrue(location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION)
 
-        module = target.GetModuleAtIndex(0)
-        num_sections = module.GetNumSections()
-        self.assertEquals(5, num_sections)
+        # Continue execution.
+        self.runCmd("c")
 
-        code_section = module.GetSectionAtIndex(0)
-        self.assertEquals("code", code_section.GetName())
-        self.assertEquals(
-            load_address | code_section.GetFileOffset(),
-            code_section.GetLoadAddress(target),
-        )
+        # Verify 1st breakpoint location is hit.
+        from lldbsuite.test.lldbutil import get_stopped_thread
 
-        debug_info_section = module.GetSectionAtIndex(1)
-        self.assertEquals(".debug_info", debug_info_section.GetName())
-        self.assertEquals(
-            LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)
+        thread = get_stopped_thread(process, lldb.eStopReasonSignal)
+        self.assertTrue(
+            thread.IsValid(), "There should be a thread stopped due to breakpoint"
         )
+        frame0 = thread.GetFrameAtIndex(0)
+        self.server.responder.SetCurrentPC(0x000002ad);
 
-        debug_abbrev_section = module.GetSectionAtIndex(2)
-        self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
-        self.assertEquals(
-            LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)
-        )
+        # Update Wasm server memory state
+        self.store_bytes(0xffe0, bytes.fromhex("0000000000003440"))
 
-        debug_line_section = module.GetSectionAtIndex(3)
-        self.assertEquals(".debug_line", debug_line_section.GetName())
-        self.assertEquals(
-            LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
+        # We should be in function 'bar'.
+        self.assertTrue(frame0.IsValid())
+        function_name = frame0.GetFunctionName()
+        self.assertIn(
+            "Calc::add(Number const&)", function_name, "Unexpected function name {}".format(function_name)
         )
 
-        debug_str_section = module.GetSectionAtIndex(4)
-        self.assertEquals(".debug_str", debug_str_section.GetName())
-        self.assertEquals(
-            LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
-        )
+        # We should be able to evaluate the expression "*this".
+        value = frame0.EvaluateExpression("*this")
+        self.assertEqual(value.GetTypeName(), "Calc")
+        field = value.GetChildAtIndex(0)
+        self.assertEqual(field.GetName(), "result_")
+        self.assertEqual(field.GetTypeName(), "double")
+        self.assertEqual(field.GetValue(), "20")
+
+        # Examine next stack frame.
+        self.runCmd("up")
+        frame1 = thread.GetSelectedFrame()
+
+        # We should be able to evaluate the expression "v2".
+        value = frame1.EvaluateExpression("v2")
+        self.assertEqual(value.GetTypeName(), "double")
+        self.assertEqual(value.GetValue(), "22")

>From db0bc4829b02aedf7a988824b38b56a1564a8125 Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Mon, 22 Jan 2024 05:57:53 -0800
Subject: [PATCH 3/3] Add test files and fix formatting

---
 .../gdb_remote_client/TestWasm.py             |  76 ++++--
 .../gdb_remote_client/calc-cpp-o0.yaml        | 251 ++++++++++++++++++
 .../gdb_remote_client/calc.cpp                |  27 ++
 3 files changed, 326 insertions(+), 28 deletions(-)
 create mode 100644 lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
 create mode 100644 lldb/test/API/functionalities/gdb_remote_client/calc.cpp

diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index f2ac2abdeef0c0..5cde7f58eb920d 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -45,11 +45,11 @@ def respond(self, packet):
         if packet[0:13] == "qRegisterInfo":
             return self.qRegisterInfo(packet[13:])
         if packet.startswith("qWasmCallStack"):
-            return self.qWasmCallStack();
+            return self.qWasmCallStack()
         if packet.startswith("qWasmLocal"):
-            return self.qWasmLocal(packet);
+            return self.qWasmLocal(packet)
         if packet.startswith("qWasmMem"):
-            return self.qWasmMem(packet);
+            return self.qWasmMem(packet)
         return MockGDBServerResponder.respond(self, packet)
 
     def qSupported(self, client_supported):
@@ -79,7 +79,7 @@ def haltReason(self):
         return "T05thread-pcs:300000083;thread:1;library:;"
 
     def readRegister(self, register):
-        assert(register == 0)
+        assert register == 0
         return format_register_value(self.current_pc)
 
     def qXferRead(self, obj, annex, offset, length):
@@ -117,12 +117,16 @@ def qfThreadInfo(self):
 
     def cont(self):
         # Continue execution. Simulates running the Wasm engine until a breakpoint is hit.
-        return "T05thread-pcs:" + format(int(self._bp_address, 16) & 0x3fffffffffffffff, 'x') + ";thread:1"
+        return (
+            "T05thread-pcs:"
+            + format(int(self._bp_address, 16) & 0x3FFFFFFFFFFFFFFF, "x")
+            + ";thread:1"
+        )
 
     def qWasmCallStack(self):
         if len(self._wasm_call_stacks) == 0:
             return ""
-        result = self._wasm_call_stacks[self._call_stack_request_count].format();
+        result = self._wasm_call_stacks[self._call_stack_request_count].format()
         self._call_stack_request_count = self._call_stack_request_count + 1
         return result
 
@@ -132,11 +136,11 @@ def qWasmLocal(self, packet):
         data = data[1].split(";")
         frame_index = data[0]
         local_index = data[1]
-        if frame_index == '0' and local_index == '4':
+        if frame_index == "0" and local_index == "4":
             return "b0ff0000"
-        if frame_index == '1' and local_index == '5':
+        if frame_index == "1" and local_index == "5":
             return "c0ff0000"
-        return "E03";
+        return "E03"
 
     def qWasmMem(self, packet):
         # Format: qWasmMem:module_id;addr;len
@@ -145,12 +149,13 @@ def qWasmMem(self, packet):
         module_id = data[0]
         addr = int(data[1], 16)
         length = int(data[2])
-        if module_id != '4':
+        if module_id != "4":
             return "E03"
-        if addr >= 0xffb8 and addr < 0x10000:
-            chunk = self._memory_view[addr:addr+length]
+        if addr >= 0xFFB8 and addr < 0x10000:
+            chunk = self._memory_view[addr : addr + length]
             return chunk.hex()
-        return "E03";
+        return "E03"
+
 
 class WasmStackFrame:
     pass
@@ -162,6 +167,7 @@ def __init__(self, module_id, address):
     def format(self):
         return format_register_value(make_code_address(self._module_id, self._address))
 
+
 class WasmCallStack:
     pass
 
@@ -174,6 +180,7 @@ def format(self):
             result += frame.format()
         return result
 
+
 class WasmMemorySpan:
     pass
 
@@ -197,7 +204,7 @@ def connect_to_wasm_engine(self, target):
         return process
 
     def store_bytes(self, offset, bytes_obj):
-        chunk = self.memory_view[offset:offset+len(bytes_obj)]
+        chunk = self.memory_view[offset : offset + len(bytes_obj)]
         for i in range(len(bytes_obj)):
             chunk[i] = bytes_obj[i]
 
@@ -326,7 +333,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
         self.assertEquals(
             LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
         )
-    
+
     @skipIfAsan
     @skipIfXmlSupportMissing
     def test_simple_wasm_debugging_session(self):
@@ -339,21 +346,32 @@ def test_simple_wasm_debugging_session(self):
 
         self.memory = bytearray(65536)
         self.memory_view = memoryview(self.memory)
-        self.store_bytes(0xffb8, bytes.fromhex("d8ff0000"))
-        self.store_bytes(0xffbc, bytes.fromhex("e0ff0000"))
-        self.store_bytes(0xffe0, bytes.fromhex("0000000000000000"))
-        self.store_bytes(0xffe8, bytes.fromhex("0000000000004540"))
-        self.store_bytes(0xfff0, bytes.fromhex("0000000000003640"))
-        self.store_bytes(0xfff8, bytes.fromhex("0000000000003440"))
+        self.store_bytes(0xFFB8, bytes.fromhex("d8ff0000"))
+        self.store_bytes(0xFFBC, bytes.fromhex("e0ff0000"))
+        self.store_bytes(0xFFE0, bytes.fromhex("0000000000000000"))
+        self.store_bytes(0xFFE8, bytes.fromhex("0000000000004540"))
+        self.store_bytes(0xFFF0, bytes.fromhex("0000000000003640"))
+        self.store_bytes(0xFFF8, bytes.fromhex("0000000000003440"))
 
         call_stacks = [
-            WasmCallStack([WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)]),
-            WasmCallStack([WasmStackFrame(4, 0x000002ad), WasmStackFrame(4, 0x0000014a), WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)])
+            WasmCallStack(
+                [WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000F)]
+            ),
+            WasmCallStack(
+                [
+                    WasmStackFrame(4, 0x000002AD),
+                    WasmStackFrame(4, 0x0000014A),
+                    WasmStackFrame(3, 0x00000083),
+                    WasmStackFrame(3, 0x0000000F),
+                ]
+            ),
         ]
-        self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks, self.memory_view)
+        self.server.responder = MyResponder(
+            obj_path, "test_wasm", call_stacks, self.memory_view
+        )
 
         target = self.dbg.CreateTarget("")
-        breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9);
+        breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9)
 
         process = self.connect_to_wasm_engine(target)
         lldbutil.expect_state_changes(
@@ -375,16 +393,18 @@ def test_simple_wasm_debugging_session(self):
             thread.IsValid(), "There should be a thread stopped due to breakpoint"
         )
         frame0 = thread.GetFrameAtIndex(0)
-        self.server.responder.SetCurrentPC(0x000002ad);
+        self.server.responder.SetCurrentPC(0x000002AD)
 
         # Update Wasm server memory state
-        self.store_bytes(0xffe0, bytes.fromhex("0000000000003440"))
+        self.store_bytes(0xFFE0, bytes.fromhex("0000000000003440"))
 
         # We should be in function 'bar'.
         self.assertTrue(frame0.IsValid())
         function_name = frame0.GetFunctionName()
         self.assertIn(
-            "Calc::add(Number const&)", function_name, "Unexpected function name {}".format(function_name)
+            "Calc::add(Number const&)",
+            function_name,
+            "Unexpected function name {}".format(function_name),
         )
 
         # We should be able to evaluate the expression "*this".
diff --git a/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml b/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
new file mode 100644
index 00000000000000..ec7b99f19106fe
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
@@ -0,0 +1,251 @@
+--- !WASM
+FileHeader:
+  Version:         0x1
+Sections:
+  - Type:            TYPE
+    Signatures:
+      - Index:           0
+        ParamTypes:
+          - F64
+          - F64
+          - F64
+        ReturnTypes:
+          - I32
+      - Index:           1
+        ParamTypes:
+          - I32
+        ReturnTypes:
+          - I32
+      - Index:           2
+        ParamTypes:
+          - I32
+          - F64
+        ReturnTypes:
+          - I32
+      - Index:           3
+        ParamTypes:
+          - I32
+          - I32
+        ReturnTypes:     []
+      - Index:           4
+        ParamTypes:
+          - I32
+        ReturnTypes:
+          - F64
+  - Type:            FUNCTION
+    FunctionTypes:   [ 0, 1, 2, 3, 4 ]
+  - Type:            TABLE
+    Tables:
+      - Index:           0
+        ElemType:        FUNCREF
+        Limits:
+          Flags:           [ HAS_MAX ]
+          Minimum:         0x1
+          Maximum:         0x1
+  - Type:            MEMORY
+    Memories:
+      - Flags:           [ HAS_MAX ]
+        Minimum:         0x100
+        Maximum:         0x100
+  - Type:            GLOBAL
+    Globals:
+      - Index:           0
+        Type:            I32
+        Mutable:         true
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           65536
+  - Type:            EXPORT
+    Exports:
+      - Name:            memory
+        Kind:            MEMORY
+        Index:           0
+      - Name:            test_sum
+        Kind:            FUNCTION
+        Index:           0
+      - Name:            __indirect_function_table
+        Kind:            TABLE
+        Index:           0
+  - Type:            CODE
+    Functions:
+      - Index:           0
+        Locals:
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+        Body:            2300210341C0002104200320046B21052005240020052000390338200520013903302005200239032841202106200520066A210720072108200810011A20052B033821094118210A2005200A6A210B200B210C200C200910021A4120210D2005200D6A210E200E210F41182110200520106A211120112112200F2012100320052B0330211341102114200520146A2115201521162016201310021A41202117200520176A2118201821194110211A2005201A6A211B201B211C2019201C10034120211D2005201D6A211E201E211F201F100421202005202039030820052B0328212120052B0308212220212022612123410121242023202471212541C0002126200520266A21272027240020250F0B
+      - Index:           1
+        Locals:
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+        Body:            2300210141102102200120026B21032003200036020C200328020C2104410021052005B721062004200639030020040F0B
+      - Index:           2
+        Locals:
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+        Body:            2300210241102103200220036B21042004200036020C20042001390300200428020C210520042B030021062005200639030020050F0B
+      - Index:           3
+        Locals:
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            F64
+            Count:           1
+        Body:            2300210241102103200220036B21042004200036020C20042001360208200428020C21052004280208210620062B0300210720052B0300210820082007A02109200520093903000F0B
+      - Index:           4
+        Locals:
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            F64
+            Count:           1
+          - Type:            I32
+            Count:           1
+          - Type:            I32
+            Count:           1
+        Body:            2300210141102102200120026B210320032400200320003602042003280204210420042B0300210541082106200320066A2107200721082008200510021A20032B030821094110210A2003200A6A210B200B240020090F0B
+  - Type:            CUSTOM
+    Name:            name
+    FunctionNames:
+      - Index:           0
+        Name:            test_sum
+      - Index:           1
+        Name:            'Calc::Calc()'
+      - Index:           2
+        Name:            'Number::Number(double)'
+      - Index:           3
+        Name:            'Calc::add(Number const&)'
+      - Index:           4
+        Name:            'Calc::result() const'
+    GlobalNames:
+      - Index:           0
+        Name:            __stack_pointer
+  - Type:            CUSTOM
+    Name:            .debug_abbrev
+    Payload:         011101250E1305030E10171B0E110155170000021301360B030E0B0B3A0B3B0B0000030D00030E49133A0B3B0B380B0000042E01030E3A0B3B0B3C193F19000005050049133419000006050049130000072400030E3E0B0B0B0000080F0049130000090D00030E49133A0B3B0B380B320B00000A2E016E0E030E3A0B3B0B3C193F1900000B2E016E0E030E3A0B3B0B49133C193F1900000C2E01030E3C1934193F1900000D1000491300000E2600491300000F2E01110112064018030E3A0B3B0B49133F1900001005000218030E3A0B3B0B491300001134000218030E3A0B3B0B49130000122E0111011206401864133A0B3B0B6E0E471300001305000218030E491334190000142E01110112064018641347130000152E0111011206401864136E0E4713000000
+  - Type:            CUSTOM
+    Name:            .debug_info
+    Payload:         E40100000400000000000401F60000002100970000000000000020000000000000000000000002059000000008010103E90000004E000000010300049000000001020555000000064E000000000007B4000000040808260000000205DC00000008010609E10000004E000000011000030A80000000C4000000010705A700000006AC000000000B000000006D000000010B2600000005B6000000000CDC00000005A70000000000085A0000000DB10000000E2600000008BB0000000E5A0000000F030000005A01000004ED00059FA00000000114D101000010029138F300000001144E00000010029130F000000001144E00000010029128BB00000001144E00000011029120D700000001155A000000110291086D00000001182600000000125E0100003E00000004ED00039F3B0100000106130000009B0000001302910C74000000D80100000014DF0100005A00000004ED00049F5E010000700000001302910C74000000D801000010029108790000000107AC00000000159D0100004100000004ED00049F93010000C80000003B0000001302910C74000000DD01000010029100AE00000001024E00000000143A0200006F00000004ED00039FC4010000860000001302910474000000E20100000007A90000000201085A000000082600000008BB00000000
+  - Type:            CUSTOM
+    Name:            .debug_ranges
+    Payload:         030000005D0100005E0100009C010000DF010000390200009D010000DE0100003A020000A90200000000000000000000
+  - Type:            CUSTOM
+    Name:            .debug_str
+    Payload:         5F5A4E4B3443616C6336726573756C744576005F5A4E3443616C634332457600513A5C70616F6C6F7365764D5346545C6C6C766D2D70726F6A6563745C6C6C64625C746573745C4150495C66756E6374696F6E616C69746965735C6764625F72656D6F74655F636C69656E7400726573756C740074686973006E756D626572005F5A4E3443616C633361646445524B364E756D6265720063616C632E63707000746573745F73756D00626F6F6C0076616C756500646F75626C6500657870656374656400616464005F5A4E364E756D626572433245640063616C630043616C6300726573756C745F0076616C75655F00763200763100636C616E672076657273696F6E2031382E302E30202868747470733A2F2F6769746875622E636F6D2F6C6C766D2F6C6C766D2D70726F6A65637420373535303166353336323464653932616166636532663164613639386232343961373239336463372900
+  - Type:            CUSTOM
+    Name:            .debug_line
+    Payload:         A701000004002B000000010101FB0E0D0001010101000000010000010063616C632E637070000000003C737464696E3E00000000000005020300000003130100050277000000030105080A010005028B000000030105130100050292000000050C0601000502A8000000050801000502CC000000030105130601000502D3000000050C0601000502E90000000508010005020D010000030105180601000502290100000301050A0100050230010000051D0601000502370100000513010005023E0100000503010005025D0100000001010005025E01000003050100050288010000030A050A0A010005029801000003760508010005029C0100000001010005029D010000030101000502CC01000005210A01000502D3010000051A0601000502DA010000052901000502DE010000000101000502DF01000003060100050214020000030105100A010005021B0200000517060100050222020000050D0100050237020000030105030601000502390200000001010005023A020000030A0100050272020000030105130A0100050279020000050C06010005028F020000050501000502A9020000000101
+...
diff --git a/lldb/test/API/functionalities/gdb_remote_client/calc.cpp b/lldb/test/API/functionalities/gdb_remote_client/calc.cpp
new file mode 100644
index 00000000000000..7e4c53c42cb500
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/calc.cpp
@@ -0,0 +1,27 @@
+struct Number {
+  Number(double value) : value_(value) {}
+  double value_;
+};
+
+struct Calc {
+  void add(const Number& number) {
+    result_ += number.value_;
+  }
+
+  Number result() const {
+    return Number(result_);
+  }
+
+ private:
+  double result_ = 0;
+};
+
+extern "C" {
+bool test_sum(double v1, double v2, double expected) {
+  Calc calc;
+  calc.add(Number(v1));
+  calc.add(Number(v2));
+  Number result = calc.result();
+  return expected == result.value_;
+}
+}
\ No newline at end of file



More information about the lldb-commits mailing list