Skip to content

[lldb] Implement RegisterContextWasm #151056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 31, 2025

Conversation

JDevlieghere
Copy link
Member

@JDevlieghere JDevlieghere commented Jul 28, 2025

This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement support for DW_OP_WASM_location in the DWARF
expression evaluator (#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.

@llvmbot
Copy link
Member

llvmbot commented Jul 28, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement supprot for DW_OP_WASM_location in the DWARF
expression evaluator (#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.


Patch is 97.48 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/151056.diff

32 Files Affected:

  • (modified) lldb/docs/resources/lldbgdbremote.md (+55)
  • (modified) lldb/include/lldb/Expression/DWARFExpression.h (+8-4)
  • (modified) lldb/packages/Python/lldbsuite/test/lldbgdbclient.py (+2-2)
  • (modified) lldb/source/Expression/DWARFExpression.cpp (+36-4)
  • (modified) lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp (+5-1)
  • (modified) lldb/source/Plugins/Process/CMakeLists.txt (+1)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp (+7-2)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h (+2)
  • (added) lldb/source/Plugins/Process/wasm/CMakeLists.txt (+11)
  • (added) lldb/source/Plugins/Process/wasm/ProcessWasm.cpp (+194)
  • (added) lldb/source/Plugins/Process/wasm/ProcessWasm.h (+106)
  • (added) lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp (+128)
  • (added) lldb/source/Plugins/Process/wasm/RegisterContextWasm.h (+90)
  • (added) lldb/source/Plugins/Process/wasm/ThreadWasm.cpp (+54)
  • (added) lldb/source/Plugins/Process/wasm/ThreadWasm.h (+41)
  • (added) lldb/source/Plugins/Process/wasm/UnwindWasm.cpp (+85)
  • (added) lldb/source/Plugins/Process/wasm/UnwindWasm.h (+51)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/CMakeLists.txt (+1)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp (+3-1)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h (+5-3)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp (+4)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h (+3)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.cpp (+5-3)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h (+2-1)
  • (added) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileWasm.cpp (+78)
  • (added) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileWasm.h (+34)
  • (modified) lldb/source/Target/Platform.cpp (+7)
  • (modified) lldb/test/API/functionalities/gdb_remote_client/TestWasm.py (+156-52)
  • (added) lldb/test/API/functionalities/gdb_remote_client/simple.c (+10)
  • (added) lldb/test/API/functionalities/gdb_remote_client/simple.yaml (+228)
  • (modified) lldb/unittests/Expression/CMakeLists.txt (+2)
  • (modified) lldb/unittests/Expression/DWARFExpressionTest.cpp (+409-147)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 80c68091ecd07..0b63d56d37acf 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -1998,6 +1998,61 @@ threads (live system debug) / cores (JTAG) in your program have
 stopped and allows LLDB to display and control your program
 correctly.
 
+## qWasmCallStack
+
+Get the Wasm call stack for the given thread id. This returns a hex-encoded
+list of PC values, one for each frame of the call stack. To match the Wasm
+specification, the addresses are encoded in little endian byte order, even if
+the endian of the Wasm runtime's host is not little endian.
+
+```
+send packet: $qWasmCallStack:202dbe040#08
+read packet: $9c01000000000040e501000000000040fe01000000000040#
+```
+
+**Priority to Implement:** Only required for Wasm support. This packed is
+supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
+and [V8](https://v8.dev) Wasm runtimes.
+
+## qWasmGlobal
+
+Get the value of a Wasm global variable for the given frame index at the given
+variable index. This returns a hex-encoded value.
+
+send packet: qWasmGlobal:frame_index;index
+read packet: ...
+
+**Priority to Implement:** Only required for Wasm support. This packed is
+supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
+and [V8](https://v8.dev) Wasm runtimes.
+
+
+## qWasmLocal
+
+Get the value of a Wasm function argument or local variable for the given frame
+index at the given variable index. This returns a hex-encoded value.
+
+send packet: qWasmGlobal:frame_index;index
+read packet: ...
+
+**Priority to Implement:** Only required for Wasm support. This packed is
+supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
+and [V8](https://v8.dev) Wasm runtimes.
+
+
+## qWasmStackValue
+
+Get the value of a Wasm local variable from the Wasm operand stack, for the
+given frame index at the given variable index. This returns a hex-encoded
+value.
+
+send packet: qWasmStackValue:frame_index;index
+read packet: ...
+
+**Priority to Implement:** Only required for Wasm support. This packed is
+supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
+and [V8](https://v8.dev) Wasm runtimes.
+
 ## qWatchpointSupportInfo
 
 Get the number of hardware watchpoints available on the remote target.
diff --git a/lldb/include/lldb/Expression/DWARFExpression.h b/lldb/include/lldb/Expression/DWARFExpression.h
index 37853c0b5a8fc..8fcc5d37b91c9 100644
--- a/lldb/include/lldb/Expression/DWARFExpression.h
+++ b/lldb/include/lldb/Expression/DWARFExpression.h
@@ -52,10 +52,10 @@ class DWARFExpression {
     GetVendorDWARFOpcodeSize(const DataExtractor &data,
                              const lldb::offset_t data_offset,
                              const uint8_t op) const = 0;
-    virtual bool ParseVendorDWARFOpcode(uint8_t op,
-                                        const DataExtractor &opcodes,
-                                        lldb::offset_t &offset,
-                                        Stack &stack) const = 0;
+    virtual bool
+    ParseVendorDWARFOpcode(uint8_t op, const DataExtractor &opcodes,
+                           lldb::offset_t &offset, RegisterContext *reg_ctx,
+                           lldb::RegisterKind reg_kind, Stack &stack) const = 0;
 
     Delegate(const Delegate &) = delete;
     Delegate &operator=(const Delegate &) = delete;
@@ -163,6 +163,10 @@ class DWARFExpression {
 
   bool MatchesOperand(StackFrame &frame, const Instruction::Operand &op) const;
 
+  static llvm::Error ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
+                                               lldb::RegisterKind reg_kind,
+                                               uint32_t reg_num, Value &value);
+
 private:
   /// A data extractor capable of reading opcode bytes
   DataExtractor m_data;
diff --git a/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py b/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
index 459460b84fbae..599f7878e6edb 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
@@ -45,7 +45,7 @@ def createTarget(self, yaml_path):
         self.yaml2obj(yaml_path, obj_path)
         return self.dbg.CreateTarget(obj_path)
 
-    def connect(self, target):
+    def connect(self, target, plugin="gdb-remote"):
         """
         Create a process by connecting to the mock GDB server.
 
@@ -54,7 +54,7 @@ def connect(self, target):
         listener = self.dbg.GetListener()
         error = lldb.SBError()
         process = target.ConnectRemote(
-            listener, self.server.get_connect_url(), "gdb-remote", error
+            listener, self.server.get_connect_url(), plugin, error
         )
         self.assertTrue(error.Success(), error.description)
         self.assertTrue(process, PROCESS_IS_VALID)
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index 79bc6c87fa9c5..68093e9710a28 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -91,9 +91,10 @@ void DWARFExpression::SetRegisterKind(RegisterKind reg_kind) {
   m_reg_kind = reg_kind;
 }
 
-static llvm::Error ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
-                                             lldb::RegisterKind reg_kind,
-                                             uint32_t reg_num, Value &value) {
+llvm::Error
+DWARFExpression::ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
+                                           lldb::RegisterKind reg_kind,
+                                           uint32_t reg_num, Value &value) {
   if (reg_ctx == nullptr)
     return llvm::createStringError("no register context in frame");
 
@@ -2300,9 +2301,40 @@ llvm::Expected<Value> 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 = 2; // Global
+      } else {
+        index = opcodes.GetULEB128(&offset);
+      }
+
+      reg_num = (((wasm_op + 1) & 0x03) << 30) | (index & 0x3fffffff);
+
+      if (llvm::Error error =
+              ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, tmp))
+        return std::move(error);
+      stack.push_back(tmp);
+      break;
+    }
+
     default:
       if (dwarf_cu) {
-        if (dwarf_cu->ParseVendorDWARFOpcode(op, opcodes, offset, stack)) {
+        if (dwarf_cu->ParseVendorDWARFOpcode(op, opcodes, offset, reg_ctx,
+                                             reg_kind, stack)) {
           break;
         }
       }
diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
index 67963a790a4fe..b1efd25949379 100644
--- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
+++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
@@ -376,9 +376,13 @@ DataExtractor ObjectFileWasm::ReadImageData(offset_t offset, uint32_t size) {
         DataBufferSP buffer_sp(data_up.release());
         data.SetData(buffer_sp, 0, buffer_sp->GetByteSize());
       }
+    } else if (offset < m_data.GetByteSize()) {
+      size =
+          std::min(static_cast<uint64_t>(size), m_data.GetByteSize() - offset);
+      return DataExtractor(m_data.GetDataStart() + offset, size, GetByteOrder(),
+                           GetAddressByteSize());
     }
   }
-
   data.SetByteOrder(GetByteOrder());
   return data;
 }
diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt
index bd9b1b86dbf13..3413360e975fb 100644
--- a/lldb/source/Plugins/Process/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/CMakeLists.txt
@@ -29,3 +29,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 a2c34ddfc252e..14dfdec6a6f62 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -323,6 +323,11 @@ ProcessGDBRemote::~ProcessGDBRemote() {
   KillDebugserverProcess();
 }
 
+std::shared_ptr<ThreadGDBRemote>
+ProcessGDBRemote::CreateThread(lldb::tid_t tid) {
+  return std::make_shared<ThreadGDBRemote>(*this, tid);
+}
+
 bool ProcessGDBRemote::ParsePythonTargetDefinition(
     const FileSpec &target_definition_fspec) {
   ScriptInterpreter *interpreter =
@@ -1594,7 +1599,7 @@ bool ProcessGDBRemote::DoUpdateThreadList(ThreadList &old_thread_list,
       ThreadSP thread_sp(
           old_thread_list_copy.RemoveThreadByProtocolID(tid, false));
       if (!thread_sp) {
-        thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
+        thread_sp = CreateThread(tid);
         LLDB_LOGV(log, "Making new thread: {0} for thread ID: {1:x}.",
                   thread_sp.get(), thread_sp->GetID());
       } else {
@@ -1726,7 +1731,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 7ae33837fd067..7c3dfb179a4b3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -246,6 +246,8 @@ class ProcessGDBRemote : public Process,
 
   ProcessGDBRemote(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
 
+  virtual std::shared_ptr<ThreadGDBRemote> CreateThread(lldb::tid_t tid);
+
   bool SupportsMemoryTagging() override;
 
   /// Broadcaster event bits definitions.
diff --git a/lldb/source/Plugins/Process/wasm/CMakeLists.txt b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
new file mode 100644
index 0000000000000..779b97ec90d08
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginProcessWasm PLUGIN
+  ProcessWasm.cpp
+  RegisterContextWasm.cpp
+  ThreadWasm.cpp
+  UnwindWasm.cpp
+
+  LINK_LIBS
+    lldbCore
+  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 0000000000000..e244ab10f9faf
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -0,0 +1,194 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/Core/Value.h"
+#include "lldb/Utility/DataBufferHeap.h"
+
+#include "lldb/Target/UnixSignals.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+using namespace lldb_private::wasm;
+
+LLDB_PLUGIN_DEFINE(ProcessWasm)
+
+ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
+    : ProcessGDBRemote(target_sp, listener_sp) {
+  assert(target_sp);
+  // Wasm doesn't have any Unix-like signals as a platform concept, but pretend
+  // like it does to appease LLDB.
+  m_unix_signals_sp = UnixSignals::Create(target_sp->GetArchitecture());
+}
+
+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() { return "wasm"; }
+
+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) {
+  if (crash_file_path == nullptr)
+    return std::make_shared<ProcessWasm>(target_sp, listener_sp);
+  return {};
+}
+
+bool ProcessWasm::CanDebug(lldb::TargetSP target_sp,
+                           bool plugin_specified_by_name) {
+  if (plugin_specified_by_name)
+    return true;
+
+  if (Module *exe_module = target_sp->GetExecutableModulePointer()) {
+    if (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:
+    error.FromErrorStringWithFormat(
+        "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
+    return 0;
+  }
+}
+
+llvm::Expected<std::vector<lldb::addr_t>>
+ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
+  StreamString packet;
+  packet.Printf("qWasmCallStack:");
+  packet.Printf("%llx", tid);
+
+  StringExtractorGDBRemote response;
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      GDBRemoteCommunication::PacketResult::Success)
+    return llvm::createStringError("failed to send qWasmCallStack");
+
+  if (!response.IsNormalResponse())
+    return llvm::createStringError("failed to get response for qWasmCallStack");
+
+  WritableDataBufferSP data_buffer_sp =
+      std::make_shared<DataBufferHeap>(response.GetStringRef().size() / 2, 0);
+  const size_t bytes = response.GetHexBytes(data_buffer_sp->GetData(), '\xcc');
+  if (bytes == 0 || bytes % sizeof(uint64_t) != 0)
+    return llvm::createStringError("invalid response for qWasmCallStack");
+
+  // To match the Wasm specification, the addresses are encoded in little endian
+  // byte order.
+  DataExtractor data(data_buffer_sp, lldb::eByteOrderLittle,
+                     GetAddressByteSize());
+  lldb::offset_t offset = 0;
+  std::vector<lldb::addr_t> call_stack_pcs;
+  while (offset < bytes)
+    call_stack_pcs.push_back(data.GetU64(&offset));
+
+  return call_stack_pcs;
+}
+
+llvm::Expected<lldb::DataBufferSP> ProcessWasm::GetWasmLocal(int frame_index,
+                                                             int index) {
+  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 llvm::createStringError("failed to send qWasmLocal");
+
+  if (!response.IsNormalResponse())
+    return llvm::createStringError("failed to get response for qWasmLocal");
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  return buffer_sp;
+}
+
+llvm::Expected<lldb::DataBufferSP> ProcessWasm::GetWasmGlobal(int frame_index,
+                                                              int index) {
+  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 llvm::createStringError("failed to send qWasmGlobal");
+
+  if (!response.IsNormalResponse())
+    return llvm::createStringError("failed to get response for qWasmGlobal");
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  return buffer_sp;
+}
+
+llvm::Expected<lldb::DataBufferSP>
+ProcessWasm::GetWasmStackValue(int frame_index, int index) {
+  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 llvm::createStringError("failed to send qWasmStackValue");
+
+  if (!response.IsNormalResponse())
+    return llvm::createStringError(
+        "failed to get response for qWasmStackValue");
+
+  WritableDataBufferSP buffer_sp(
+      new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+  response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+  return buffer_sp;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
new file mode 100644
index 0000000000000..81682eac27997
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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"
+
+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.
+enum WasmAddressType : uint8_t { Memory = 0x00, Object = 0x01, Invalid = 0xff };
+
+/// For the purpose of debugging, we can represent all these separated 32-bit
+/// address spaces with a single virtual 64-bit address space. The
+/// wasm_addr_t provides this encoding using bitfields.
+struct wasm_addr_t {
+  uint64_t offset : 32;
+  uint64_t module_id : 30;
+  uint64_t type : 2;
+
+  wasm_addr_t(lldb::addr_t addr)
+      : offset(addr & 0x00000000ffffffff),
+        module_id((addr & 0x00ffffff00000000) >> 32), type(addr >> 62) {}
+
+  wasm_addr_t(WasmAddressType type, uint32_t module_id, uint32_t offset)
+      : offset(offset), module_id(module_id), type(type) {}
+
+  WasmAddressType GetType() { return static_cast<WasmAddressType>(type); }
+
+  operator lldb::addr_t() { return *(uint64_t *)this; }
+};
+
+static_assert(sizeof(wasm_addr_t) == 8, "");
+
+/// ProcessWasm provides the access to the Wasm program state
+/// retrieved from the Wasm engine.
+c...
[truncated]

@JDevlieghere
Copy link
Member Author

This PR contains a copy of the changes from #150143 and #151010. Please review the first two commits in those PRs respectively.

ProcessWasm::GetWasmStackValue(int frame_index, int index) {
StreamString packet;
packet.PutCString("qWasmStackValue:");
packet.Printf("%d;%d", frame_index, index);
Copy link
Collaborator

@jasonmolenda jasonmolenda Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these fields sent in base10 or base16? I wasn't clear from the packet description in the doc. same thing for qWasmGlobal and qWasmLocal. (I'd expect to see a base16 without a 0x prefix in a gdb-remote packet for a field like this)
(I've tried to sneak some 0x's in to gdb-remote packets but Pavel always notices and slaps my hand. it's easy to not see the base when you're testing with small numbers.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impl https://github.com/bytecodealliance/wasm-micro-runtime/blob/23799a2cb6778896e5e7797d17bc826e1afdbf6e/core/iwasm/libraries/debug-engine/handler.c#L165
seems to be expecting base10. quite unusual for the gdb remote serial protocol, but what you gonna do, if that's the choice they made.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our doc is now clear on this topic.

Feels like they encoded it based on how easy it was to read each bit. Indexes are usually numbers and addresses are better in hex? It is what it is anyway.

Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to be a stacked PR, so I just looked at the last commit.

@DavidSpickett
Copy link
Collaborator

This PR implements a register context for Wasm, which uses virtual registers to resolve Wasm local, globals and stack values.

As I understand it, we internally are adding virtual registers, because that's the metaphor lldb is used to. For users, register read --all still only shows the PC, as that's the only real register.

Correct?

Because I think if we're going to show what are conceptually variables as registers, that's worth a discussion, but I think this is not what you are doing here. This is just an internal convenience.

@JDevlieghere
Copy link
Member Author

As I understand it, we internally are adding virtual registers, because that's the metaphor lldb is used to. For users, register read --all still only shows the PC, as that's the only real register.

Correct?

Because I think if we're going to show what are conceptually variables as registers, that's worth a discussion, but I think this is not what you are doing here. This is just an internal convenience.

Because we report zero registers, we don't show any registers when you do reg read -a. I guess we could support one reigster set with one register, but I'm not convinced it's worth it. You can still show the PC with p $pc though.

@JDevlieghere JDevlieghere force-pushed the qWasmRegisterContext branch from 9148f6b to fed4d8b Compare July 29, 2025 23:43
@JDevlieghere JDevlieghere requested a review from MaxDesiatov July 29, 2025 23:50
@DavidSpickett
Copy link
Collaborator

Because we report zero registers, we don't show any registers when you do reg read -a. I guess we could support one reigster set with one register, but I'm not convinced it's worth it. You can still show the PC with p $pc though.

That's fine then.

The pedant line here is that if Wasm calls it literally a register in the spec, then it should show up in register read. But I have not checked and if no one notices it's missing then no point changing it.

It's not like people outside of us manually write the PC that often anyway.

Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supprot -> support, in the PR description.

LGTM.

This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement supprot for `DW_OP_WASM_location` in the DWARF
expression evaluator (llvm#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.
@JDevlieghere JDevlieghere force-pushed the qWasmRegisterContext branch from b35b16b to 1c70fa8 Compare July 30, 2025 23:33
@JDevlieghere JDevlieghere merged commit f623702 into llvm:main Jul 31, 2025
10 checks passed
@JDevlieghere JDevlieghere deleted the qWasmRegisterContext branch July 31, 2025 02:51
JDevlieghere added a commit to swiftlang/llvm-project that referenced this pull request Aug 6, 2025
This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement support for `DW_OP_WASM_location` in the DWARF
expression evaluator (llvm#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.

(cherry picked from commit f623702)
krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement support for `DW_OP_WASM_location` in the DWARF
expression evaluator (llvm#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants