https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/213176

>From b7e2d1ec547ab6311c74967fcb779389fd359d76 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Thu, 30 Jul 2026 17:00:49 -0700
Subject: [PATCH 1/2] [lldb] Scope Wasm global reads to a module instance

A Wasm global belongs to a module instance, not to a frame. The global
index space is per instance, and DW_OP_WASM_location's global operand
indexes the index space of the instance whose code is being evaluated,
so two instances of one module have separate globals. qWasmGlobal takes
a frame index instead, which leaves a global of an instance with no
active frame out of reach.

Name the instance instead: qWasmGlobal:<index>;instance:<id>;. The id is
the module id LLDB already carries in bits 61:32 of a Wasm address, so
nothing has to be enumerated first. A stub opts in by advertising
qWasmInstance+ in its qSupported response, which covers every Wasm
packet whose scope is an instance rather than a frame, so a query for
other instance-scoped state does not need a packet of its own. A stub
that does not advertise it keeps getting the frame form.

Linear memory needs no suffix: a Wasm address already carries the id of
the instance it points into, which the documentation now spells out
along with the object space tag a reported load address has to carry.
Every value the id field can hold names an instance, zero included, so
the sentinel for no instance lives outside the 30-bit id range.

Naming an address space explicitly, as #206370 proposes, is a separate
feature and stays out of this packet until it lands.

Fixes #212833
---
 lldb/docs/resources/lldbgdbremote.md          |  61 +-
 .../Plugins/ObjectFile/wasm/WasmAddress.h     |  16 +
 .../GDBRemoteCommunicationClient.cpp          |  10 +
 .../gdb-remote/GDBRemoteCommunicationClient.h |   6 +
 .../Plugins/Process/wasm/ProcessWasm.cpp      | 119 ++-
 .../source/Plugins/Process/wasm/ProcessWasm.h |  28 +-
 .../Process/wasm/RegisterContextWasm.cpp      |  14 +-
 .../Process/wasm/RegisterContextWasm.h        |   7 +
 .../Plugins/Process/wasm/UnwindWasm.cpp       |   6 +-
 .../gdb_remote_client/TestWasm.py             | 691 +++++++++++++++---
 .../simple_global_frame_base.yaml             | 238 ++++++
 11 files changed, 1064 insertions(+), 132 deletions(-)
 create mode 100644 
lldb/test/API/functionalities/gdb_remote_client/simple_global_frame_base.yaml

diff --git a/lldb/docs/resources/lldbgdbremote.md 
b/lldb/docs/resources/lldbgdbremote.md
index 93090c19c5ec0..bc39292427c45 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2638,6 +2638,20 @@ The packet below are supported by the
 [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) and
 [V8](https://v8.dev) Wasm runtimes.
 
+An address is 64 bits wide: an address space tag in bits 63:62, the id of the
+module instance the address belongs to in bits 61:32, and a 32-bit offset into
+that space. The tag is 0 for linear memory and 1 for the object space, which
+holds the module image, so bit 63 is always clear on the wire. A stub therefore
+reports the load address of an instance in `qXfer:libraries:read` as
+`(1 << 62) | (<instance id> << 32)`, the base of its module in the object 
space,
+and the same id appears in the PCs returned by `qWasmCallStack`. An id is 
unique
+among live instances, and zero is an id like any other. LLDB keys a module on
+the name it is reported under, so each instance needs a name of its own.
+
+An address the running code computed, such as one relative to a frame base,
+carries no id, and a stub serves it from the instance the current thread is
+executing.
+
 
 ### qWasmCallStack
 
@@ -2657,19 +2671,58 @@ stack traces.
 
 ### qWasmGlobal
 
-Get the value of a Wasm global variable for the given frame index at the given
-variable index. The indexes are encoded as base 10. The result is a hex-encoded
-little-endian value of the global.
+Get the value of a Wasm global variable at the given variable index. The 
indexes
+are encoded as base 10. The result is a hex-encoded little-endian value of the
+whole global, or `E<nn>`.
+
+A global index space belongs to a module instance, so an index only names a
+global together with the instance to read it from. A stub that advertises
+`qWasmInstance+` is named that instance directly:
 
 ```
-send packet: $qWasmGlobal:0;2#cb
+send packet: $qWasmGlobal:2;instance:16;#32
 read packet: $e0030100#b9
 ```
 
+A stub that does not is given a frame index instead, which only reaches the
+instance that frame is executing:
+
+```
+send packet: $qWasmGlobal:0;2#31
+read packet: $e0030100#b9
+```
+
+The first field is a global index in the first form and a frame index in the
+second, so a stub tells the two apart by the `<key>:<value>` suffix. An
+unrecognized instance id must be answered with an error rather than with 
another
+instance's global.
+
 **Priority to Implement:** Only required for Wasm support. Necessary to show
 variables.
 
 
+### qWasmInstance (qSupported feature)
+
+A stub advertises `qWasmInstance+` when a query may name the module instance it
+is about, rather than only the instance some frame is executing. LLDB needs 
this
+to read a global of an instance with no frame on the stack, which it finds by
+name in the debug info of a module.
+
+```
+send packet: qSupported:xmlRegisters=i386,arm,mips
+read packet: qXfer:libraries:read+;qWasmInstance+;PacketSize=1000
+```
+
+An instance is named with a `;instance:<id>;` suffix in place of a frame index,
+in which the id is encoded as base 10. `qWasmGlobal` is the only packet that
+carries it today, because a global is the only Wasm state with no address to
+identify its instance. A later query for instance-scoped state carries the same
+suffix rather than adding a packet of its own.
+
+**Priority to Implement:** Only required for Wasm support. Necessary to show 
the
+globals of a module instance that has no active frame.
+
+
 ### qWasmLocal
 
 Get the value of a Wasm function argument or local variable for the given frame
diff --git a/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h 
b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h
index 8c731de38e36f..17916857580b0 100644
--- a/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h
+++ b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h
@@ -9,6 +9,7 @@
 #ifndef LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_WASMADDRESS_H
 #define LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_WASMADDRESS_H
 
+#include "lldb/lldb-defines.h"
 #include "lldb/lldb-types.h"
 #include <cstdint>
 
@@ -56,6 +57,13 @@ static constexpr uint64_t kWasmModuleIDMask =
 static constexpr uint64_t kWasmAddressTypeMask =
     MakeFieldMask(kWasmAddressTypeBits, kWasmAddressTypeShift);
 
+/// A value that names no module. Every value the id field can hold names a
+/// module, zero included, so the sentinel has to come from outside that range.
+static constexpr uint32_t kWasmInvalidModuleID = UINT32_MAX;
+
+static_assert(kWasmInvalidModuleID > (kWasmModuleIDMask >> kWasmModuleIDShift),
+              "the sentinel has to fall outside the range of a module id");
+
 /// 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.
@@ -81,6 +89,14 @@ struct wasm_addr_t {
 
 static_assert(sizeof(wasm_addr_t) == 8, "");
 
+/// The module an address belongs to, or kWasmInvalidModuleID for an invalid
+/// address.
+inline uint32_t GetWasmModuleID(lldb::addr_t addr) {
+  if (addr == LLDB_INVALID_ADDRESS)
+    return kWasmInvalidModuleID;
+  return wasm_addr_t(addr).GetModuleID();
+}
+
 } // namespace wasm
 } // namespace lldb_private
 
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index b440869f25984..1397abf02d7d6 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -233,6 +233,12 @@ bool 
GDBRemoteCommunicationClient::GetAcceleratorPluginsSupported() {
   return m_supports_accelerator_plugins == eLazyBoolYes;
 }
 
+bool GDBRemoteCommunicationClient::GetWasmInstanceSupported() {
+  if (m_supports_wasm_instance == eLazyBoolCalculate)
+    GetRemoteQSupported();
+  return m_supports_wasm_instance == eLazyBoolYes;
+}
+
 llvm::Expected<std::vector<AcceleratorActions>>
 GDBRemoteCommunicationClient::GetAcceleratorInitializeActions() {
   // Get the initial actions (e.g. breakpoints to set) requested by any
@@ -431,6 +437,7 @@ void 
GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
     m_supports_jModulesInfo = true;
     m_supports_multi_mem_read = eLazyBoolCalculate;
     m_supports_multi_breakpoint = eLazyBoolCalculate;
+    m_supports_wasm_instance = eLazyBoolCalculate;
   }
 
   // These flags should be reset when we first connect to a GDB server and when
@@ -460,6 +467,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
   m_supports_multi_mem_read = eLazyBoolNo;
   m_supports_multi_breakpoint = eLazyBoolNo;
   m_supports_accelerator_plugins = eLazyBoolNo;
+  m_supports_wasm_instance = eLazyBoolNo;
 
   m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
                                   // not, we assume no limit
@@ -527,6 +535,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
         m_supports_multi_breakpoint = eLazyBoolYes;
       else if (x == "accelerator-plugins+")
         m_supports_accelerator_plugins = eLazyBoolYes;
+      else if (x == "qWasmInstance+")
+        m_supports_wasm_instance = eLazyBoolYes;
       // Look for a list of compressions in the features list e.g.
       // 
qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
       // deflate,lzma
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 3a0a34f840c21..cc16949eef000 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -359,6 +359,11 @@ class GDBRemoteCommunicationClient : public 
GDBRemoteClientBase {
 
   bool GetAcceleratorPluginsSupported();
 
+  /// Whether the WebAssembly stub can be told which module instance to read
+  /// from, which it advertises with "qWasmInstance+" in its qSupported
+  /// response.
+  bool GetWasmInstanceSupported();
+
   /// Send the "jAcceleratorPluginInitialize" packet and return the actions
   /// requested by each accelerator plugin installed in lldb-server. The packet
   /// is only sent if the lldb-server advertised accelerator plugin support via
@@ -614,6 +619,7 @@ class GDBRemoteCommunicationClient : public 
GDBRemoteClientBase {
   LazyBool m_supports_multi_mem_read = eLazyBoolCalculate;
   LazyBool m_supports_multi_breakpoint = eLazyBoolCalculate;
   LazyBool m_supports_accelerator_plugins = eLazyBoolCalculate;
+  LazyBool m_supports_wasm_instance = eLazyBoolCalculate;
 
   bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
       m_supports_qUserName : 1, m_supports_qGroupName : 1,
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index bf454b2ca4476..2c627548c8aaf 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -13,6 +13,7 @@
 #include "lldb/Core/Value.h"
 #include "lldb/Target/StackFrame.h"
 #include "lldb/Utility/DataBufferHeap.h"
+#include "llvm/Support/ErrorExtras.h"
 #include <cstring>
 
 #include "lldb/Target/UnixSignals.h"
@@ -90,24 +91,14 @@ std::shared_ptr<ThreadGDBRemote> 
ProcessWasm::CreateThread(lldb::tid_t tid) {
 
 size_t ProcessWasm::ReadGlobal(uint32_t module_id, uint32_t index, void *buf,
                                size_t size, Status &error) {
-  // FIXME: The module id is what should select the instance holding the 
global,
-  // but the qWasmGlobal packet takes a frame index instead, so the selected
-  // frame has to stand in for the instance. That leaves a global in an 
instance
-  // with no active frame out of reach. See
-  // https://github.com/llvm/llvm-project/issues/212833.
-  ThreadSP thread = GetThreadList().GetSelectedThread();
-  StackFrameSP frame =
-      thread ? thread->GetSelectedFrame(DoNoSelectMostRelevantFrame) : nullptr;
-  if (!frame) {
-    error = Status::FromErrorStringWithFormatv(
-        "Wasm global read failed: no frame to read global {0} of module {1:x} "
-        "from",
-        index, module_id);
-    return 0;
-  }
+  // Looking for a frame drives the unwinder, so only pay for it when the read
+  // has to go through one.
+  const uint32_t frame_index = CanNameInstance(module_id)
+                                   ? LLDB_INVALID_INDEX32
+                                   : GetFallbackFrameIndex(module_id);
 
   llvm::Expected<lldb::DataBufferSP> buffer =
-      GetWasmVariable(eWasmTagGlobal, frame->GetConcreteFrameIndex(), index);
+      GetWasmGlobal(module_id, index, frame_index);
   if (!buffer) {
     error = Status::FromError(buffer.takeError());
     return 0;
@@ -127,8 +118,28 @@ size_t ProcessWasm::ReadGlobal(uint32_t module_id, 
uint32_t index, void *buf,
   return size;
 }
 
+uint32_t ProcessWasm::GetFallbackFrameIndex(uint32_t module_id) {
+  ThreadSP thread = GetThreadList().GetSelectedThread();
+  StackFrameSP frame =
+      thread ? thread->GetSelectedFrame(DoNoSelectMostRelevantFrame) : nullptr;
+  if (!frame)
+    return LLDB_INVALID_INDEX32;
+
+  // A frame can only stand in for the module the stub reports it executing.
+  const uint32_t frame_index = frame->GetConcreteFrameIndex();
+  ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(*thread);
+  if (GetWasmModuleID(wasm_thread.GetConcreteFramePC(frame_index)) != 
module_id)
+    return LLDB_INVALID_INDEX32;
+
+  return frame_index;
+}
+
 size_t ProcessWasm::ReadMemory(const ProcessAddress &process_addr, void *buf,
                                size_t size, Status &error) {
+  // A caller may reuse one error across reads, as the overridden
+  // Process::ReadMemory allows.
+  error.Clear();
+
   lldb::addr_t vm_addr = process_addr.GetValue();
   wasm_addr_t wasm_addr(vm_addr);
 
@@ -184,34 +195,68 @@ ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
 }
 
 llvm::Expected<lldb::DataBufferSP>
-ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index,
-                             int index) {
-  StreamString packet;
-  switch (kind) {
-  case eWasmTagLocal:
-    packet.Printf("qWasmLocal:");
-    break;
-  case eWasmTagGlobal:
-    packet.Printf("qWasmGlobal:");
-    break;
-  case eWasmTagOperandStack:
-    packet.PutCString("qWasmStackValue:");
-    break;
-  case eWasmTagNotAWasmLocation:
-    return llvm::createStringError("not a Wasm location");
-  }
-  packet.Printf("%d;%d", frame_index, index);
-
+ProcessWasm::SendWasmValueQuery(llvm::StringRef packet) {
   StringExtractorGDBRemote response;
-  if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+  if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response) !=
       GDBRemoteCommunication::PacketResult::Success)
-    return llvm::createStringError("failed to send Wasm variable");
+    return llvm::createStringErrorV("failed to send {0}", packet);
 
   if (!response.IsNormalResponse())
-    return llvm::createStringError("failed to get response for Wasm variable");
+    return llvm::createStringErrorV("failed to get response for {0}", packet);
 
   WritableDataBufferSP buffer_sp(
       new DataBufferHeap(response.GetStringRef().size() / 2, 0));
   response.GetHexBytes(buffer_sp->GetData(), '\xcc');
   return buffer_sp;
 }
+
+llvm::Expected<lldb::DataBufferSP>
+ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind,
+                             uint32_t frame_index, uint32_t index) {
+  switch (kind) {
+  case eWasmTagLocal:
+    return SendWasmValueQuery(
+        llvm::formatv("qWasmLocal:{0};{1}", frame_index, index).str());
+  case eWasmTagOperandStack:
+    return SendWasmValueQuery(
+        llvm::formatv("qWasmStackValue:{0};{1}", frame_index, index).str());
+  case eWasmTagGlobal:
+    // A global belongs to a module rather than a frame. See GetWasmGlobal.
+    return llvm::createStringError("a Wasm global does not belong to a frame");
+  case eWasmTagNotAWasmLocation:
+    return llvm::createStringError("not a Wasm location");
+  }
+  llvm_unreachable("unhandled Wasm virtual register kind");
+}
+
+llvm::Expected<lldb::DataBufferSP>
+ProcessWasm::GetWasmGlobal(uint32_t module_id, uint32_t index,
+                           uint32_t frame_index) {
+  // The global index space belongs to a module instance, so an index only 
names
+  // a global together with the instance holding it.
+  if (CanNameInstance(module_id))
+    return SendWasmValueQuery(
+        llvm::formatv("qWasmGlobal:{0};instance:{1};", index, 
module_id).str());
+
+  // A frame stands in for the instance it is executing only where that 
instance
+  // cannot be named.
+  if (frame_index != LLDB_INVALID_INDEX32)
+    return SendWasmValueQuery(
+        llvm::formatv("qWasmGlobal:{0};{1}", frame_index, index).str());
+
+  if (module_id == kWasmInvalidModuleID)
+    return llvm::createStringErrorV(
+        "global {0} belongs to no known module instance, and no frame is "
+        "executing one to read it through",
+        index);
+
+  return llvm::createStringErrorV(
+      "the Wasm stub can only read a global through a frame, and no frame is "
+      "executing module {0:x} to read global {1} through",
+      module_id, index);
+}
+
+bool ProcessWasm::CanNameInstance(uint32_t module_id) {
+  return module_id != kWasmInvalidModuleID &&
+         m_gdb_comm.GetWasmInstanceSupported();
+}
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index 1e73e42af412a..d4a9a9a2be3bd 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -46,10 +46,19 @@ class ProcessWasm : public 
process_gdb_remote::ProcessGDBRemote {
   /// Retrieve the current call stack from the WebAssembly remote process.
   llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack(lldb::tid_t tid);
 
-  /// Query the value of a WebAssembly variable from the WebAssembly
-  /// remote process.
+  /// Query the value of a frame-scoped WebAssembly variable, which is a local
+  /// or a value on the operand stack.
   llvm::Expected<lldb::DataBufferSP>
-  GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index, int index);
+  GetWasmVariable(WasmVirtualRegisterKinds kind, uint32_t frame_index,
+                  uint32_t index);
+
+  /// Query the value of a WebAssembly global. The global index space is per
+  /// module, so an index only names a global together with \a module_id.
+  ///
+  /// \a frame_index only serves a stub that cannot be told which instance to
+  /// read. Pass LLDB_INVALID_INDEX32 when no frame can stand in.
+  llvm::Expected<lldb::DataBufferSP>
+  GetWasmGlobal(uint32_t module_id, uint32_t index, uint32_t frame_index);
 
 protected:
   std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
@@ -59,12 +68,25 @@ class ProcessWasm : public 
process_gdb_remote::ProcessGDBRemote {
   friend class UnwindWasm;
   friend class ThreadWasm;
 
+  /// Ask the WebAssembly stub for a single value, which comes back as the
+  /// hex-encoded bytes of the whole value.
+  llvm::Expected<lldb::DataBufferSP> SendWasmValueQuery(llvm::StringRef 
packet);
+
   /// Read a WebAssembly global by its index in the global index space of the
   /// module it belongs to. The index space is per module, so an index only
   /// names a global together with the module it is an index into.
   size_t ReadGlobal(uint32_t module_id, uint32_t index, void *buf, size_t size,
                     Status &error);
 
+  /// The frame to read a global of \a module_id through, or
+  /// LLDB_INVALID_INDEX32 when no frame can stand in for that module.
+  uint32_t GetFallbackFrameIndex(uint32_t module_id);
+
+  /// Whether the instance holding a global can be named to the stub, which
+  /// needs both a valid id to name it by and a stub that accepts one. Where it
+  /// cannot, a frame executing that instance has to stand in for it.
+  bool CanNameInstance(uint32_t module_id);
+
   lldb::DynamicRegisterInfoSP &GetRegisterInfo() { return m_register_info_sp; }
 
   ProcessWasm(const ProcessWasm &);
diff --git a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp 
b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
index a05fbd56e8680..ee50b3c3bdbbc 100644
--- a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
@@ -29,6 +29,10 @@ RegisterContextWasm::RegisterContextWasm(ThreadGDBRemote 
&thread,
 
 RegisterContextWasm::~RegisterContextWasm() = default;
 
+uint32_t RegisterContextWasm::GetModuleID() {
+  return GetWasmModuleID(GetPC(LLDB_INVALID_ADDRESS));
+}
+
 uint32_t RegisterContextWasm::ConvertRegisterKindToRegisterNumber(
     lldb::RegisterKind kind, uint32_t num) {
   return num;
@@ -92,11 +96,15 @@ bool RegisterContextWasm::ReadRegister(const RegisterInfo 
*reg_info,
       static_cast<WasmVirtualRegisterInfo *>(
           const_cast<RegisterInfo *>(reg_info));
 
-  llvm::Expected<DataBufferSP> maybe_buffer = process->GetWasmVariable(
-      wasm_reg_info->kind, frame_index, wasm_reg_info->index);
+  llvm::Expected<DataBufferSP> maybe_buffer =
+      wasm_reg_info->kind == eWasmTagGlobal
+          ? process->GetWasmGlobal(GetModuleID(), wasm_reg_info->index,
+                                   frame_index)
+          : process->GetWasmVariable(wasm_reg_info->kind, frame_index,
+                                     wasm_reg_info->index);
   if (!maybe_buffer) {
     LLDB_LOG_ERROR(GetLog(LLDBLog::Process), maybe_buffer.takeError(),
-                   "Failed to read Wasm local: {0}");
+                   "Failed to read Wasm value: {0}");
     return false;
   }
 
diff --git a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h 
b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
index 5f047be432a05..ea2cffe811130 100644
--- a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
+++ b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
@@ -60,6 +60,13 @@ class RegisterContextWasm
                      const RegisterValue &value) override;
 
 private:
+  /// The module whose code this context's frame is executing, which a virtual
+  /// register number has no room to carry. The frame's program counter is 
where
+  /// it comes from. Resolved on each use rather than held onto: the innermost
+  /// frame's context is the thread's own and outlives a stop, so a cached
+  /// answer would go on naming the module of a previous stop.
+  uint32_t GetModuleID();
+
   std::unordered_map<size_t, std::unique_ptr<WasmVirtualRegisterInfo>>
       m_register_map;
 };
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp 
b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
index df3b146efca14..700ce950fd635 100644
--- a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
@@ -26,7 +26,8 @@ static constexpr lldb::addr_t kWasmSyntheticCFABase = 
0x40000000;
 
 lldb::RegisterContextSP
 UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) {
-  if (m_frames.size() <= frame->GetFrameIndex())
+  const uint32_t concrete_frame_idx = frame->GetConcreteFrameIndex();
+  if (m_frames.size() <= concrete_frame_idx)
     return lldb::RegisterContextSP();
 
   ThreadSP thread = frame->GetThread();
@@ -34,8 +35,7 @@ 
UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) {
   ProcessWasm *wasm_process =
       static_cast<ProcessWasm *>(thread->GetProcess().get());
 
-  return std::make_shared<RegisterContextWasm>(*gdb_thread,
-                                               frame->GetConcreteFrameIndex(),
+  return std::make_shared<RegisterContextWasm>(*gdb_thread, concrete_frame_idx,
                                                
wasm_process->GetRegisterInfo());
 }
 
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py 
b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index 4e4a21d9416b1..1d284be6fd7ed 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -6,16 +6,97 @@
 from lldbsuite.test.gdbclientutils import *
 from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
 
-MODULE_ID = 4
-LOAD_ADDRESS = MODULE_ID << 32
+# Ids of the instances the fake stub loads. An id is written in base 10, both
+# in a packet and in the library list it is reported through, so these read
+# differently in base 16, which a single digit would not.
+MODULE_ID = 16
+SECOND_MODULE_ID = 26
+
+# The address spaces an address can point into, in the bits above the id of the
+# instance the address belongs to. The object space holds the module image, so
+# that is the space a module is loaded in.
+WASM_OBJECT_ADDRESS = 1 << 62
+WASM_GLOBAL_ADDRESS = 2 << 62
+WASM_ID_MASK = 0x3FFFFFFF
+
+LOAD_ADDRESS = WASM_OBJECT_ADDRESS | (MODULE_ID << 32)
 WASM_LOCAL_ADDR = 0x103E0
 
-# The synthetic address space globals are given, in which the offset is the
-# index into the global index space rather than a byte offset.
-WASM_GLOBAL_ADDRESS = 2 << 62
+# The key under which a packet names the module instance whose state it reads.
+# The key form is also what tells the two shapes of qWasmGlobal apart, since 
the
+# first field is a global index in one and a frame index in the other.
+INSTANCE_KEY = "instance:"
+
+# Globals the fake stub holds, as index -> (size in bytes, value). Every
+# instance has an index space of its own, so these are the globals of one
+# instance and the same index names a different global in another. An index is
+# written in base 10 like an instance id, so one of them reads differently in
+# base 16.
+WASM_GLOBALS = {0: (4, 0x2A), 1: (8, 0xDEADBEEF), 26: (4, 0x33)}
+SECOND_WASM_GLOBALS = {0: (4, 0x1234), 1: (8, 0xFEEDFACE)}
+
+# Bytes of the fake stack frame a frame base points at, holding the values of 
the
+# parameters of "add" at the offsets its DWARF gives them, and above them those
+# of the variables of "main". A Wasm stack grows down, so the frame of a caller
+# sits above the frame of what it called.
+WASM_FRAME_BYTES = bytes.fromhex(
+    "0000000000000000020000000100000000000000020000000100000000000000"
+)
+WASM_CALLER_FRAME_ADDR = WASM_LOCAL_ADDR + 16
+
+# The globals simple_global_frame_base.yaml gives the two functions of the 
module
+# as their frame base. Each function is based on a global of its own, so a read
+# of the wrong one does not land in the frame it belongs to.
+INNER_FRAME_BASE_GLOBAL_INDEX = 0
+OUTER_FRAME_BASE_GLOBAL_INDEX = 1
+FRAME_BASE_GLOBALS = {
+    INNER_FRAME_BASE_GLOBAL_INDEX: (4, WASM_LOCAL_ADDR),
+    OUTER_FRAME_BASE_GLOBAL_INDEX: (4, WASM_CALLER_FRAME_ADDR),
+}
+
+
+class WasmModule:
+    """
+    A module the fake stub has loaded, together with the globals held by the
+    instance it was loaded as.
+    """
 
-# Globals the fake engine holds, as index -> (size in bytes, value).
-WASM_GLOBALS = {0: (4, 0x2A), 1: (8, 0xDEADBEEF)}
+    def __init__(self, obj_path, name, module_id=MODULE_ID, 
global_values=None):
+        self.obj_path = obj_path
+        self.name = name
+        self.module_id = module_id
+        self.load_address = WASM_OBJECT_ADDRESS | (module_id << 32)
+        self.global_values = WASM_GLOBALS if global_values is None else 
global_values
+        self._image = None
+
+    def get_image(self):
+        """
+        The bytes of the module itself, which is what the stub serves in the
+        range the module is loaded at.
+        """
+        if self._image is None:
+            with open(self.obj_path, mode="rb") as file:
+                self._image = file.read()
+        return self._image
+
+    def encode_global(self, global_index):
+        """
+        Encode the global at the given index, or an error when this instance
+        holds no such global. A global is transferred as a whole value, in
+        little-endian order.
+        """
+        value = self.global_values.get(global_index)
+        if value is None:
+            return "E03"
+        size, val = value
+        return val.to_bytes(size, "little").hex()
+
+
+def global_read_packet(global_index, module_id=MODULE_ID):
+    """
+    The packet that reads a global from the module instance holding it.
+    """
+    return f"qWasmGlobal:{global_index};{INSTANCE_KEY}{module_id};"
 
 
 def format_register_value(val):
@@ -34,17 +115,21 @@ def format_register_value(val):
 
 
 class WasmStackFrame:
-    def __init__(self, address):
+    def __init__(self, address, load_address=LOAD_ADDRESS):
         self._address = address
+        self._load_address = load_address
 
     def __str__(self):
-        return format_register_value(LOAD_ADDRESS | self._address)
+        return format_register_value(self._load_address | self._address)
 
 
 class WasmCallStack:
     def __init__(self, wasm_stack_frames):
         self._wasm_stack_frames = wasm_stack_frames
 
+    def __len__(self):
+        return len(self._wasm_stack_frames)
+
     def __str__(self):
         result = ""
         for frame in self._wasm_stack_frames:
@@ -80,38 +165,80 @@ def contains(self, addr):
 class MyResponder(MockGDBServerResponder):
     current_pc = LOAD_ADDRESS | 0x01AD
 
-    def __init__(self, obj_path, module_name="", wasm_call_stacks=[], 
memory=None):
-        self._obj_path = obj_path
-        self._module_name = module_name or obj_path
+    def __init__(
+        self,
+        modules,
+        wasm_call_stacks=[],
+        memory=None,
+        supports_instance=True,
+    ):
+        self._modules = modules
         self._wasm_call_stacks = wasm_call_stacks
         self._call_stack_request_count = 0
+        self._reported_frames = 0
         self._memory = memory
+        self._supports_instance = supports_instance
         MockGDBServerResponder.__init__(self)
 
-    def respond(self, packet):
-        if packet[0:13] == "qRegisterInfo":
-            return self.qRegisterInfo(packet[13:])
+    def other(self, packet):
         if packet.startswith("qWasmCallStack"):
             return self.qWasmCallStack()
         if packet.startswith("qWasmLocal"):
             return self.qWasmLocal(packet)
         if packet.startswith("qWasmGlobal"):
             return self.qWasmGlobal(packet)
-        return MockGDBServerResponder.respond(self, packet)
+        return MockGDBServerResponder.other(self, packet)
+
+    def module_with_id(self, module_id):
+        """
+        The module the stub loaded with the given id, if it loaded one. Nothing
+        about a module is shared with another, so an id that names none has no
+        code to read and no globals to hand out.
+        """
+        for module in self._modules:
+            if module.module_id == module_id:
+                return module
+        return None
 
     def qWasmGlobal(self, packet):
-        # Format: qWasmGlobal:frame_index;index
-        data = packet.split(":")[1].split(";")
-        _, global_index = data
-        value = WASM_GLOBALS.get(int(global_index))
-        if value is None:
-            return "E03"
-        # A global is transferred as a whole value, in little-endian order.
-        size, val = value
-        return val.to_bytes(size, "little").hex()
+        """
+        Read a global. A client that can name the instance holding it does so
+        with the instance suffix, and one that cannot names a frame.
+
+        Format: qWasmGlobal:index;instance:id; or
+                qWasmGlobal:frame_index;index
+        """
+        first, _, rest = packet.split(":", 1)[1].partition(";")
+
+        if rest.startswith(INSTANCE_KEY):
+            if not self._supports_instance:
+                # A stub is only asked for what it advertised.
+                return "E05"
+            # The global index space belongs to the instance, so an index only
+            # names a global together with the instance it indexes. Another
+            # instance's globals answer a different question.
+            module = self.module_with_id(int(rest[len(INSTANCE_KEY) 
:].rstrip(";")))
+            if module is None:
+                return "E04"
+            return module.encode_global(int(first))
+
+        if self._supports_instance:
+            # A frame cannot name the instance whose globals are indexed, so a
+            # stub that can be given an instance is never given a frame.
+            return "E05"
+        # A frame index that names none of the frames the stub reported is no
+        # scope to read a global through.
+        if int(first) >= self._reported_frames:
+            return "E06"
+        # A frame stands in for the instance it is executing, which with a 
single
+        # loaded module is that module.
+        return self._modules[0].encode_global(int(rest))
 
     def qSupported(self, client_supported):
-        return "qXfer:libraries:read+;PacketSize=1000;vContSupported-"
+        response = "qXfer:libraries:read+;PacketSize=1000;vContSupported-"
+        if self._supports_instance:
+            response += ";qWasmInstance+"
+        return response
 
     def qHostInfo(self):
         return ""
@@ -138,11 +265,12 @@ def readRegister(self, register):
 
     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)
+            libraries = "".join(
+                '<library name="%s"><section address="%d"/></library>'
+                % (module.name, module.load_address)
+                for module in self._modules
             )
-            return xml, False
+            return "<library-list>" + libraries + "</library-list>", False
         else:
             return None, False
 
@@ -150,19 +278,20 @@ def readMemory(self, addr, length):
         if self._memory and self._memory.contains(addr):
             chunk = self._memory.get_bytes(addr, length)
             return chunk.hex()
-        if addr < LOAD_ADDRESS:
+        # A module is loaded in the object space of its instance, so an address
+        # in that space is what asks for the module image, and the id the 
address
+        # carries picks the instance whose module answers.
+        if addr >> 62 != WASM_OBJECT_ADDRESS >> 62:
             return "E02"
-        result = ""
-        with open(self._obj_path, mode="rb") as file:
-            file_content = bytearray(file.read())
-            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
+        module = self.module_with_id((addr >> 32) & WASM_ID_MASK)
+        if module is None:
+            return "E02"
+        image = module.get_image()
+        offset = addr - module.load_address
+        if offset >= len(image):
+            return "E03"
+        end = offset + min(length, len(image) - offset)
+        return image[offset:end].hex()
 
     def setBreakpoint(self, packet):
         bp_data = packet[1:].split(",")
@@ -173,19 +302,20 @@ 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"
-        )
+        # Continue execution. Simulates running the Wasm stub until a 
breakpoint is hit.
+        # A program counter names the space and the instance it belongs to, as
+        # the address the breakpoint was set at does.
+        return "T05thread-pcs:" + self._bp_address + ";thread:1"
 
     def qWasmCallStack(self):
         if len(self._wasm_call_stacks) == 0:
             return ""
-        result = str(self._wasm_call_stacks[self._call_stack_request_count])
+        call_stack = self._wasm_call_stacks[self._call_stack_request_count]
         self._call_stack_request_count += 1
-        return result
+        # A frame is only a scope the stub can be asked about once it has
+        # reported it.
+        self._reported_frames = len(call_stack)
+        return str(call_stack)
 
     def qWasmLocal(self, packet):
         # Format: qWasmLocal:frame_index;index
@@ -201,14 +331,14 @@ class TestWasm(GDBRemoteTestBase):
     @skipIfAsan
     @skipIfXmlSupportMissing
     def test_load_module_with_embedded_symbols_from_remote(self):
-        """Test connecting to a WebAssembly engine via GDB-remote and loading 
a Wasm module with embedded DWARF symbols"""
+        """Test connecting to a WebAssembly stub via GDB-remote and loading a 
Wasm module with embedded DWARF symbols"""
 
         yaml_path = "test_wasm_embedded_debug_sections.yaml"
         yaml_base, ext = os.path.splitext(yaml_path)
         obj_path = self.getBuildArtifact(yaml_base)
         self.yaml2obj(yaml_path, obj_path)
 
-        self.server.responder = MyResponder(obj_path, "test_wasm")
+        self.server.responder = MyResponder([WasmModule(obj_path, 
"test_wasm")])
 
         target = self.dbg.CreateTarget("")
         process = self.connect(target, "wasm")
@@ -261,7 +391,7 @@ def 
test_load_module_with_embedded_symbols_from_remote(self):
     @skipIfAsan
     @skipIfXmlSupportMissing
     def test_load_module_with_stripped_symbols_from_remote(self):
-        """Test connecting to a WebAssembly engine via GDB-remote and loading 
a Wasm module with symbols stripped into a separate Wasm file"""
+        """Test connecting to a WebAssembly stub via GDB-remote and loading a 
Wasm module with symbols stripped into a separate Wasm file"""
 
         sym_yaml_path = "test_sym.yaml"
         sym_yaml_base, ext = os.path.splitext(sym_yaml_path)
@@ -273,7 +403,7 @@ def 
test_load_module_with_stripped_symbols_from_remote(self):
         obj_path = self.getBuildArtifact(yaml_base) + ".wasm"
         self.yaml2obj(yaml_path, obj_path)
 
-        self.server.responder = MyResponder(obj_path, "test_wasm")
+        self.server.responder = MyResponder([WasmModule(obj_path, 
"test_wasm")])
 
         folder, _ = os.path.split(obj_path)
         self.runCmd(
@@ -327,7 +457,7 @@ def 
test_load_module_with_stripped_symbols_from_remote(self):
     @skipIfAsan
     @skipIfXmlSupportMissing
     def test_simple_wasm_debugging_session(self):
-        """Test connecting to a WebAssembly engine via GDB-remote, loading a
+        """Test connecting to a WebAssembly stub via GDB-remote, loading a
         Wasm module with embedded DWARF symbols, setting a breakpoint and
         checking the debuggee state"""
 
@@ -350,15 +480,10 @@ def test_simple_wasm_debugging_session(self):
 
         # Create fake memory for our wasm locals.
         self.memory = FakeMemory(0x10000, 0x20000)
-        self.memory.store_bytes(
-            WASM_LOCAL_ADDR,
-            bytes.fromhex(
-                
"0000000000000000020000000100000000000000020000000100000000000000"
-            ),
-        )
+        self.memory.store_bytes(WASM_LOCAL_ADDR, WASM_FRAME_BYTES)
 
         self.server.responder = MyResponder(
-            obj_path, "test_wasm", call_stacks, self.memory
+            [WasmModule(obj_path, "test_wasm")], call_stacks, self.memory
         )
 
         target = self.dbg.CreateTarget("")
@@ -422,7 +547,9 @@ def test_max_backtrace_depth(self):
                 [WasmStackFrame(0x019C), WasmStackFrame(0x01E5), 
WasmStackFrame(0x01FE)]
             ),
         ]
-        self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks)
+        self.server.responder = MyResponder(
+            [WasmModule(obj_path, "test_wasm")], call_stacks
+        )
 
         target = self.dbg.CreateTarget("")
         process = self.connect(target, "wasm")
@@ -445,20 +572,29 @@ def test_max_backtrace_depth(self):
         self.assertEqual(thread.GetFrameAtIndex(1).GetPC(), LOAD_ADDRESS | 
0x01E5)
         self.assertFalse(thread.GetFrameAtIndex(2).IsValid())
 
-    @skipIfAsan
-    @skipIfXmlSupportMissing
-    def test_read_global(self):
-        """Test that a WebAssembly global can be read through the address its
-        module gives it, and that a read it cannot serve fails instead of
-        returning something plausible."""
-
-        yaml_path = "simple.yaml"
-        yaml_base, _ = os.path.splitext(yaml_path)
-        obj_path = self.getBuildArtifact(yaml_base)
+    def build_wasm_module(self, name, yaml_path="simple.yaml", **module_args):
+        """
+        Build a Wasm module the fake stub reports under the given name. A
+        loaded module is told apart from another by the name it is reported
+        under, so every instance has to be reported under a name of its own.
+        """
+        obj_path = self.getBuildArtifact(name)
         self.yaml2obj(yaml_path, obj_path)
-
-        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
-        self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks)
+        return WasmModule(obj_path, name, **module_args)
+
+    def connect_to_modules(
+        self, modules, call_stacks, supports_instance=True, memory=None
+    ):
+        """
+        Connect to a fake stub holding the given modules and return its target
+        and process.
+        """
+        self.server.responder = MyResponder(
+            modules,
+            call_stacks,
+            memory,
+            supports_instance=supports_instance,
+        )
 
         target = self.dbg.CreateTarget("")
         process = self.connect(target, "wasm")
@@ -466,14 +602,93 @@ def test_read_global(self):
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
 
-        # Read through the address the module gives its globals rather than a
-        # constructed one, so that the encoding the object file produces and 
the
-        # one the process decodes are checked against each other.
-        module = target.GetModuleAtIndex(0)
-        global_section = module.FindSection("global")
+        return target, process
+
+    def get_globals_address(self, target, module):
+        """
+        The address the loaded module gives its globals. Reading through it
+        rather than a constructed address checks the encoding the object file
+        produces and the one the process decodes against each other.
+        """
+        image = target.FindModule(lldb.SBFileSpec(module.name))
+        self.assertTrue(image.IsValid())
+        global_section = image.FindSection("global")
         self.assertTrue(global_section.IsValid())
         globals_addr = global_section.GetLoadAddress(target)
-        self.assertEqual(globals_addr, WASM_GLOBAL_ADDRESS | LOAD_ADDRESS)
+        # A global lives in the global index space of its instance rather than
+        # in the space the module itself is loaded in.
+        self.assertEqual(globals_addr, WASM_GLOBAL_ADDRESS | (module.module_id 
<< 32))
+        return globals_addr
+
+    def connect_to_globals(self, call_stacks, supports_instance=True):
+        """
+        Connect to a fake stub holding WASM_GLOBALS and return its process
+        together with the address its module gives its globals.
+        """
+        module = self.build_wasm_module("test_wasm")
+        target, process = self.connect_to_modules(
+            [module], call_stacks, supports_instance
+        )
+        return process, self.get_globals_address(target, module)
+
+    def connect_to_frame_base_globals(self, supports_instance=True, 
call_stacks=None):
+        """
+        Connect to a fake stub stopped in a module whose functions are based on
+        a global holding the address of their frame, and return its thread.
+        """
+        module = self.build_wasm_module(
+            "test_wasm",
+            yaml_path="simple_global_frame_base.yaml",
+            global_values=FRAME_BASE_GLOBALS,
+        )
+
+        if call_stacks is None:
+            call_stacks = [
+                WasmCallStack([WasmStackFrame(0x019C), WasmStackFrame(0x01E5)])
+            ]
+        memory = FakeMemory(0x10000, 0x20000)
+        memory.store_bytes(WASM_LOCAL_ADDR, WASM_FRAME_BYTES)
+        _, process = self.connect_to_modules(
+            [module],
+            call_stacks,
+            supports_instance=supports_instance,
+            memory=memory,
+        )
+
+        thread = process.GetThreadAtIndex(0)
+        self.assertTrue(thread.IsValid())
+        return thread
+
+    def packets_received(self, prefix):
+        """
+        The packets the client sent that start with the given prefix. An
+        assertion on the packets themselves names the ones that make it fail,
+        which an assertion on whether there are any does not.
+        """
+        received = self.server.responder.packetLog.get_received()
+        return [packet for packet in received if packet.startswith(prefix)]
+
+    def global_reads_received(self, instance):
+        """
+        The global reads the client sent that name an instance, or those that
+        name a frame instead. Both forms share a packet name, so the suffix is
+        what tells them apart.
+        """
+        return [
+            packet
+            for packet in self.packets_received("qWasmGlobal:")
+            if (INSTANCE_KEY in packet) == instance
+        ]
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global(self):
+        """Test that a WebAssembly global can be read through the address its
+        module gives it, and that a read it cannot serve fails instead of
+        returning something plausible."""
+
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
+        process, globals_addr = self.connect_to_globals(call_stacks)
 
         # A global is read as a whole value.
         error = lldb.SBError()
@@ -485,6 +700,10 @@ def test_read_global(self):
         self.assertSuccess(error)
         self.assertEqual(int.from_bytes(data, "little"), 0xDEADBEEF)
 
+        data = process.ReadMemory(globals_addr + 26, 4, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x33)
+
         # A type narrower than the global it is held in reads the low bytes,
         # which is how a char or short global is read.
         data = process.ReadMemory(globals_addr + 0, 1, error)
@@ -502,6 +721,314 @@ def test_read_global(self):
         process.ReadMemory(globals_addr + 99, 4, error)
         self.assertFalse(error.Success())
 
+        # A global is named by the instance holding it. MODULE_ID differs from
+        # every frame index, so a frame index cannot pass for an instance id.
+        self.assertPacketLogReceived(
+            [
+                global_read_packet(0),
+                global_read_packet(1),
+                global_read_packet(26),
+            ]
+        )
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_without_reported_call_stack(self):
+        """Test that a WebAssembly global can be read while the stub reports no
+        call stack for the instance holding it."""
+
+        process, globals_addr = self.connect_to_globals([])
+
+        # A thread always has a frame, which LLDB makes from the registers the
+        # stub reports when the Wasm unwinder contributes none. The program
+        # counter of that frame belongs to no module, so it is not a frame a
+        # global can be read through.
+        thread = process.GetThreadAtIndex(0)
+        self.assertTrue(thread.IsValid())
+        self.assertEqual(1, thread.GetNumFrames())
+        self.assertEqual(MyResponder.current_pc, 
thread.GetFrameAtIndex(0).GetPC())
+
+        error = lldb.SBError()
+        data = process.ReadMemory(globals_addr + 0, 4, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x2A)
+
+        data = process.ReadMemory(globals_addr + 1, 8, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0xDEADBEEF)
+
+        self.assertPacketLogReceived(
+            [
+                global_read_packet(0),
+                global_read_packet(1),
+            ]
+        )
+
+        # The stub was asked for a call stack and reported none, so the reads
+        # above were served without one. Reading a global through a frame is
+        # something else, and is what a stub that cannot be told which instance
+        # to read is limited to.
+        self.assertNotEqual([], self.packets_received("qWasmCallStack"))
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_from_multiple_instances(self):
+        """Test that a global is read from the instance that holds it when more
+        than one instance is loaded."""
+
+        module = self.build_wasm_module("test_wasm")
+        second_module = self.build_wasm_module(
+            "test_wasm_second",
+            module_id=SECOND_MODULE_ID,
+            global_values=SECOND_WASM_GLOBALS,
+        )
+
+        # Only the first instance is executing, so the frame available to read 
a
+        # global through belongs to it and cannot stand in for the other one.
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
+        target, process = self.connect_to_modules([module, second_module], 
call_stacks)
+
+        # Each instance has to be a module of its own for its globals to be its
+        # own. Collapsing the two would leave one of the load addresses unused.
+        self.assertEqual(2, target.GetNumModules())
+
+        globals_addr = self.get_globals_address(target, module)
+        second_globals_addr = self.get_globals_address(target, second_module)
+        self.assertNotEqual(globals_addr, second_globals_addr)
+
+        # The same index in either instance names a global of that instance, 
and
+        # the fake stub refuses any instance it did not load, so neither value
+        # can come from the wrong place.
+        error = lldb.SBError()
+        for addr, global_values in [
+            (globals_addr, WASM_GLOBALS),
+            (second_globals_addr, SECOND_WASM_GLOBALS),
+        ]:
+            for index, (size, value) in global_values.items():
+                data = process.ReadMemory(addr + index, size, error)
+                self.assertSuccess(error)
+                self.assertEqual(int.from_bytes(data, "little"), value)
+
+        self.assertPacketLogReceived(
+            [
+                global_read_packet(0),
+                global_read_packet(1),
+                global_read_packet(0, SECOND_MODULE_ID),
+                global_read_packet(1, SECOND_MODULE_ID),
+            ]
+        )
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_from_default_instance(self):
+        """Test that a global is read from the instance a debug session is
+        created for, which a stub reports at id zero."""
+
+        # An id of zero yields the load address a stub reports when it cannot
+        # report an instance at all, and an address the running code computed
+        # carries no id either, so zero has to name an instance.
+        module = self.build_wasm_module("test_wasm", module_id=0)
+        self.assertEqual(module.load_address, WASM_OBJECT_ADDRESS)
+
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C, 
module.load_address)])]
+        target, process = self.connect_to_modules([module], call_stacks)
+        globals_addr = self.get_globals_address(target, module)
+
+        error = lldb.SBError()
+        data = process.ReadMemory(globals_addr + 0, 4, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x2A)
+
+        # Zero names that instance rather than standing for no instance, so the
+        # read is scoped to it instead of falling back to naming a frame.
+        self.assertPacketLogReceived([global_read_packet(0, module.module_id)])
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_variable_located_through_global(self):
+        """Test that a variable an outer frame locates through a Wasm global is
+        read from the instance the frame is executing."""
+
+        thread = self.connect_to_frame_base_globals()
+        frame1 = thread.GetFrameAtIndex(1)
+        self.assertTrue(frame1.IsValid())
+        self.assertIn("main", frame1.GetFunctionName())
+
+        # A variable relative to that frame base is only reachable by reading 
the
+        # global, so these values are what came back from it.
+        i = frame1.FindVariable("i")
+        self.assertTrue(i.IsValid())
+        self.assertEqual(i.GetValueAsUnsigned(), 1)
+
+        j = frame1.FindVariable("j")
+        self.assertTrue(j.IsValid())
+        self.assertEqual(j.GetValueAsUnsigned(), 2)
+
+        # The global belongs to the instance the frame is executing, and that
+        # instance is what names it. The frame the read goes through does not.
+        self.assertPacketLogReceived(
+            [global_read_packet(OUTER_FRAME_BASE_GLOBAL_INDEX)]
+        )
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_variable_located_through_global_in_innermost_frame(self):
+        """Test that a variable the innermost frame locates through a Wasm 
global
+        is read from the instance that frame is executing."""
+
+        # The innermost frame is where a stop leaves the program, and its
+        # register context is the one the thread carries, which exists before 
any
+        # frame does. The instance that frame is executing is therefore only
+        # known once the stub has been asked for its call stack.
+        thread = self.connect_to_frame_base_globals()
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertTrue(frame0.IsValid())
+        self.assertIn("add", frame0.GetFunctionName())
+
+        a = frame0.FindVariable("a")
+        self.assertTrue(a.IsValid())
+        self.assertEqual(a.GetValueAsUnsigned(), 1)
+
+        b = frame0.FindVariable("b")
+        self.assertTrue(b.IsValid())
+        self.assertEqual(b.GetValueAsUnsigned(), 2)
+
+        self.assertPacketLogReceived(
+            [global_read_packet(INNER_FRAME_BASE_GLOBAL_INDEX)]
+        )
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def 
test_read_variable_located_through_global_without_reported_call_stack(self):
+        """Test that a variable a frame locates through a Wasm global is read
+        from the instance that frame is executing while the stub reports no 
call
+        stack."""
+
+        # LLDB makes a frame from the registers the stub reports when the Wasm
+        # unwinder contributes none. The program counter of that frame is the
+        # only place the instance it executes is recorded, so a read that 
looked
+        # for the instance anywhere else would be left without one and fall 
back
+        # to naming a frame the stub never reported.
+        thread = self.connect_to_frame_base_globals(call_stacks=[])
+        self.assertEqual(1, thread.GetNumFrames())
+
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertTrue(frame0.IsValid())
+        self.assertIn("add", frame0.GetFunctionName())
+
+        a = frame0.FindVariable("a")
+        self.assertTrue(a.IsValid())
+        self.assertEqual(a.GetValueAsUnsigned(), 1)
+
+        self.assertPacketLogReceived(
+            [global_read_packet(INNER_FRAME_BASE_GLOBAL_INDEX)]
+        )
+        self.assertEqual([], self.global_reads_received(instance=False))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_variable_located_through_global_legacy_server(self):
+        """Test that a variable the innermost frame locates through a Wasm 
global
+        is read through that frame when the stub cannot be told which instance 
to
+        read."""
+
+        thread = self.connect_to_frame_base_globals(supports_instance=False)
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertTrue(frame0.IsValid())
+        self.assertIn("add", frame0.GetFunctionName())
+
+        a = frame0.FindVariable("a")
+        self.assertTrue(a.IsValid())
+        self.assertEqual(a.GetValueAsUnsigned(), 1)
+
+        # A frame stands in for the instance it is executing, which is all 
such a
+        # stub can be asked, so the same global stays within reach through the
+        # frame.
+        self.assertPacketLogReceived(["qWasmGlobal:0;0"])
+        self.assertEqual([], self.global_reads_received(instance=True))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_legacy_server(self):
+        """Test that a global is read through the frame executing the instance
+        that holds it when the stub cannot be told which instance to read."""
+
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
+        process, globals_addr = self.connect_to_globals(
+            call_stacks, supports_instance=False
+        )
+
+        error = lldb.SBError()
+        data = process.ReadMemory(globals_addr + 0, 4, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x2A)
+
+        # A global at an index the frame index cannot be mistaken for, so that
+        # the frame and the global keep their places in the packet.
+        data = process.ReadMemory(globals_addr + 1, 8, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0xDEADBEEF)
+
+        # A stub is only asked for what it advertised, so the form it never
+        # offered is not tried on it.
+        self.assertPacketLogReceived(["qWasmGlobal:0;0", "qWasmGlobal:0;1"])
+        self.assertEqual([], self.global_reads_received(instance=True))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_legacy_server_without_reported_call_stack(self):
+        """Test that a global is out of reach of a stub that can only read a
+        global through a frame while it reports no call stack."""
+
+        process, globals_addr = self.connect_to_globals([], 
supports_instance=False)
+
+        error = lldb.SBError()
+        process.ReadMemory(globals_addr + 0, 4, error)
+        self.assertFalse(error.Success())
+        self.assertIn("can only read a global through a frame", 
error.GetCString())
+        self.assertEqual([], self.packets_received("qWasmGlobal:"))
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global_legacy_server_other_instance(self):
+        """Test that a frame executing one instance is not read as a frame of
+        another when the stub can only read a global through a frame."""
+
+        module = self.build_wasm_module("test_wasm")
+        second_module = self.build_wasm_module(
+            "test_wasm_second",
+            module_id=SECOND_MODULE_ID,
+            global_values=SECOND_WASM_GLOBALS,
+        )
+
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
+        target, process = self.connect_to_modules(
+            [module, second_module], call_stacks, supports_instance=False
+        )
+        self.assertEqual(2, target.GetNumModules())
+
+        # The only frame is executing the first instance, and a frame of one
+        # instance indexes no globals of another. Reading a global of the 
second
+        # instance fails rather than answering with a global of the first.
+        error = lldb.SBError()
+        process.ReadMemory(self.get_globals_address(target, second_module), 4, 
error)
+        self.assertFalse(error.Success())
+        self.assertIn("can only read a global through a frame", 
error.GetCString())
+
+        # The instance the frame is executing is still within reach.
+        error = lldb.SBError()
+        data = process.ReadMemory(self.get_globals_address(target, module), 4, 
error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), WASM_GLOBALS[0][1])
+
+        self.assertPacketLogReceived(["qWasmGlobal:0;0"])
+
     @skipIfXmlSupportMissing
     def test_non_wasm_process(self):
         """Test that the plugin falls back to plain GDB remote debugging when
diff --git 
a/lldb/test/API/functionalities/gdb_remote_client/simple_global_frame_base.yaml 
b/lldb/test/API/functionalities/gdb_remote_client/simple_global_frame_base.yaml
new file mode 100644
index 0000000000000..6dff712d9f539
--- /dev/null
+++ 
b/lldb/test/API/functionalities/gdb_remote_client/simple_global_frame_base.yaml
@@ -0,0 +1,238 @@
+# simple.yaml, with the frame base of both of its functions changed from a Wasm
+# local to a Wasm global:
+#
+#   "add":  DW_AT_frame_base (DW_OP_WASM_location 0x1 0x0, DW_OP_stack_value)
+#   "main": DW_AT_frame_base (DW_OP_WASM_location 0x1 0x1, DW_OP_stack_value)
+#
+# A DW_OP_WASM_location names a local, a global or an operand stack value, so
+# a frame based on a global is what makes resolving a variable in that frame
+# read a global. Each function is based on a global of its own, so a read that
+# went to the wrong one would not land in the frame it belongs to.
+--- !WASM
+FileHeader:
+  Version:         0x1
+Sections:
+  - Type:            TYPE
+    Signatures:
+      - Index:           0
+        ParamTypes:      []
+        ReturnTypes:     []
+      - Index:           1
+        ParamTypes:
+          - I32
+          - I32
+        ReturnTypes:
+          - I32
+      - Index:           2
+        ParamTypes:      []
+        ReturnTypes:
+          - I32
+  - Type:            FUNCTION
+    FunctionTypes:   [ 0, 1, 2, 1 ]
+  - Type:            TABLE
+    Tables:
+      - Index:           0
+        ElemType:        FUNCREF
+        Limits:
+          Flags:           [ HAS_MAX ]
+          Minimum:         0x1
+          Maximum:         0x1
+  - Type:            MEMORY
+    Memories:
+      - Minimum:         0x2
+  - Type:            GLOBAL
+    Globals:
+      - Index:           0
+        Type:            I32
+        Mutable:         true
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           66560
+      - Index:           1
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           1024
+      - Index:           2
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           1024
+      - Index:           3
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           1024
+      - Index:           4
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           66560
+      - Index:           5
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           1024
+      - Index:           6
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           66560
+      - Index:           7
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           131072
+      - Index:           8
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           0
+      - Index:           9
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           1
+      - Index:           10
+        Type:            I32
+        Mutable:         false
+        InitExpr:
+          Opcode:          I32_CONST
+          Value:           65536
+  - Type:            EXPORT
+    Exports:
+      - Name:            memory
+        Kind:            MEMORY
+        Index:           0
+      - Name:            __wasm_call_ctors
+        Kind:            FUNCTION
+        Index:           0
+      - Name:            add
+        Kind:            FUNCTION
+        Index:           1
+      - Name:            __original_main
+        Kind:            FUNCTION
+        Index:           2
+      - Name:            main
+        Kind:            FUNCTION
+        Index:           3
+      - Name:            __main_void
+        Kind:            FUNCTION
+        Index:           2
+      - Name:            __indirect_function_table
+        Kind:            TABLE
+        Index:           0
+      - Name:            __dso_handle
+        Kind:            GLOBAL
+        Index:           1
+      - Name:            __data_end
+        Kind:            GLOBAL
+        Index:           2
+      - Name:            __stack_low
+        Kind:            GLOBAL
+        Index:           3
+      - Name:            __stack_high
+        Kind:            GLOBAL
+        Index:           4
+      - Name:            __global_base
+        Kind:            GLOBAL
+        Index:           5
+      - Name:            __heap_base
+        Kind:            GLOBAL
+        Index:           6
+      - Name:            __heap_end
+        Kind:            GLOBAL
+        Index:           7
+      - Name:            __memory_base
+        Kind:            GLOBAL
+        Index:           8
+      - Name:            __table_base
+        Kind:            GLOBAL
+        Index:           9
+      - Name:            __wasm_first_page_end
+        Kind:            GLOBAL
+        Index:           10
+  - Type:            CODE
+    Functions:
+      - Index:           0
+        Locals:          []
+        Body:            0B
+      - Index:           1
+        Locals:
+          - Type:            I32
+            Count:           1
+        Body:            
23808080800041106B21022002200036020C20022001360208200228020C20022802086A0F0B
+      - Index:           2
+        Locals:
+          - Type:            I32
+            Count:           2
+        Body:            
23808080800041106B210020002480808080002000410036020C2000410136020820004102360204200028020820002802041081808080002101200041106A24808080800020010F0B
+      - Index:           3
+        Locals:          []
+        Body:            1082808080000F0B
+  - Type:            CUSTOM
+    Name:            .debug_abbrev
+    Payload:         
011101250E1305030E10171B0E110155170000022E01110112064018030E3A0B3B0B271949133F1900000305000218030E3A0B3B0B49130000042E01110112064018030E3A0B3B0B49133F1900000534000218030E3A0B3B0B49130000062400030E3E0B0B0B000000
+  - Type:            CUSTOM
+    Name:            .debug_info
+    Payload:         
940000000400000000000401620000001D0055000000000000000D000000000000000000000002050000002900000004ED01009F510000000101900000000302910C60000000010190000000030291085E00000001019000000000042F0000004C00000004ED01019F04000000010690000000050291080B0000000107900000000502910409000000010890000000000600000000050400
+  - Type:            CUSTOM
+    Name:            .debug_ranges
+    Payload:         050000002E0000002F0000007B0000000000000000000000
+  - Type:            CUSTOM
+    Name:            .debug_str
+    Payload:         
696E74006D61696E006A0069002F55736572732F6A6F6E61732F7761736D2D6D6963726F2D72756E74696D652F70726F647563742D6D696E692F706C6174666F726D732F64617277696E2F6275696C64006164640073696D706C652E630062006100636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A4A4465766C696567686572652F6C6C766D2D70726F6A6563742E67697420343161363839613132323834633834623632383933393461356338306264636534383733656466302900
+  - Type:            CUSTOM
+    Name:            .debug_line
+    Payload:         
62000000040020000000010101FB0E0D0001010101000000010000010073696D706C652E6300000000000005020500000001050A0A08AE050E0658050C5805032002020001010005022F0000001705070A08BB75050E7505110658050A58050382020F000101
+  - Type:            CUSTOM
+    Name:            name
+    FunctionNames:
+      - Index:           0
+        Name:            __wasm_call_ctors
+      - Index:           1
+        Name:            add
+      - Index:           2
+        Name:            __original_main
+      - Index:           3
+        Name:            main
+    GlobalNames:
+      - Index:           0
+        Name:            __stack_pointer
+  - Type:            CUSTOM
+    Name:            producers
+    Languages:
+      - Name:            C11
+        Version:         ''
+    Tools:
+      - Name:            clang
+        Version:         '22.0.0git'
+  - Type:            CUSTOM
+    Name:            target_features
+    Features:
+      - Prefix:          USED
+        Name:            bulk-memory
+      - Prefix:          USED
+        Name:            bulk-memory-opt
+      - Prefix:          USED
+        Name:            call-indirect-overlong
+      - Prefix:          USED
+        Name:            multivalue
+      - Prefix:          USED
+        Name:            mutable-globals
+      - Prefix:          USED
+        Name:            nontrapping-fptoint
+      - Prefix:          USED
+        Name:            reference-types
+      - Prefix:          USED
+        Name:            sign-ext
+...

>From e3d5c58f2ba4bff151048b0613c0e8843f6c9798 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Wed, 19 Aug 2026 16:36:46 -0700
Subject: [PATCH 2/2] Address Felipe's feedback

---
 lldb/docs/resources/lldbgdbremote.md          | 16 +--
 .../Plugins/Process/wasm/ProcessWasm.cpp      | 98 ++++++++++---------
 .../source/Plugins/Process/wasm/ProcessWasm.h | 42 ++++----
 .../Process/wasm/RegisterContextWasm.cpp      | 21 +++-
 .../Process/wasm/RegisterContextWasm.h        |  9 +-
 5 files changed, 102 insertions(+), 84 deletions(-)

diff --git a/lldb/docs/resources/lldbgdbremote.md 
b/lldb/docs/resources/lldbgdbremote.md
index bc39292427c45..5a3f83801ac40 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2676,26 +2676,26 @@ are encoded as base 10. The result is a hex-encoded 
little-endian value of the
 whole global, or `E<nn>`.
 
 A global index space belongs to a module instance, so an index only names a
-global together with the instance to read it from. A stub that advertises
-`qWasmInstance+` is named that instance directly:
+global together with the module instance to read it from. A stub that 
advertises
+`qWasmInstance+` requires that module instance to be named explicitly:
 
 ```
 send packet: $qWasmGlobal:2;instance:16;#32
 read packet: $e0030100#b9
 ```
 
-A stub that does not is given a frame index instead, which only reaches the
-instance that frame is executing:
+A stub that does not advertise `qWasmInstance+` is given a frame index instead,
+which only reaches the module instance that frame is executing:
 
 ```
 send packet: $qWasmGlobal:0;2#31
 read packet: $e0030100#b9
 ```
 
-The first field is a global index in the first form and a frame index in the
-second, so a stub tells the two apart by the `<key>:<value>` suffix. An
-unrecognized instance id must be answered with an error rather than with 
another
-instance's global.
+A stub tells the two apart by the presence of the `instance:` key, the only key
+this packet takes. Where it is absent, the first field is a frame index rather
+than a global index. An unrecognized instance id must be answered with an error
+rather than with another instance's global.
 
 **Priority to Implement:** Only required for Wasm support. Necessary to show
 variables.
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index 2c627548c8aaf..3e2bf05e00a0c 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -89,24 +89,16 @@ std::shared_ptr<ThreadGDBRemote> 
ProcessWasm::CreateThread(lldb::tid_t tid) {
   return std::make_shared<ThreadWasm>(*this, tid);
 }
 
-size_t ProcessWasm::ReadGlobal(uint32_t module_id, uint32_t index, void *buf,
-                               size_t size, Status &error) {
-  // Looking for a frame drives the unwinder, so only pay for it when the read
-  // has to go through one.
-  const uint32_t frame_index = CanNameInstance(module_id)
-                                   ? LLDB_INVALID_INDEX32
-                                   : GetFallbackFrameIndex(module_id);
-
-  llvm::Expected<lldb::DataBufferSP> buffer =
-      GetWasmGlobal(module_id, index, frame_index);
-  if (!buffer) {
-    error = Status::FromError(buffer.takeError());
+static size_t CopyGlobal(llvm::Expected<lldb::DataBufferSP> global, void *buf,
+                         size_t size, Status &error) {
+  if (!global) {
+    error = Status::FromError(global.takeError());
     return 0;
   }
 
   // A global comes back whole. Reading more than it holds would have to come
   // from somewhere else, and the next index is not adjacent storage.
-  const size_t global_size = (*buffer)->GetByteSize();
+  const size_t global_size = (*global)->GetByteSize();
   if (size > global_size) {
     error = Status::FromErrorStringWithFormatv(
         "Wasm global read failed: requested {0} bytes from a {1}-byte global",
@@ -114,24 +106,50 @@ size_t ProcessWasm::ReadGlobal(uint32_t module_id, 
uint32_t index, void *buf,
     return 0;
   }
 
-  std::memcpy(buf, (*buffer)->GetBytes(), size);
+  std::memcpy(buf, (*global)->GetBytes(), size);
   return size;
 }
 
-uint32_t ProcessWasm::GetFallbackFrameIndex(uint32_t module_id) {
+size_t ProcessWasm::ReadGlobal(uint32_t module_id, uint32_t index, void *buf,
+                               size_t size, Status &error) {
+  if (CanNameInstance(module_id))
+    return CopyGlobal(GetWasmGlobalForModule(module_id, index), buf, size,
+                      error);
+
+  // Looking for a frame drives the unwinder, so only pay for it where the
+  // instance cannot be named.
+  llvm::Expected<uint32_t> frame_index = GetFallbackFrameIndex(module_id);
+  if (!frame_index) {
+    error = Status::FromError(frame_index.takeError());
+    return 0;
+  }
+
+  return CopyGlobal(GetWasmGlobalForFrame(*frame_index, index), buf, size,
+                    error);
+}
+
+llvm::Expected<uint32_t>
+ProcessWasm::GetFallbackFrameIndex(uint32_t module_id) {
+  if (module_id == kWasmInvalidModuleID)
+    return llvm::createStringError(
+        "the global belongs to no known module instance");
+
   ThreadSP thread = GetThreadList().GetSelectedThread();
   StackFrameSP frame =
       thread ? thread->GetSelectedFrame(DoNoSelectMostRelevantFrame) : nullptr;
-  if (!frame)
-    return LLDB_INVALID_INDEX32;
-
-  // A frame can only stand in for the module the stub reports it executing.
-  const uint32_t frame_index = frame->GetConcreteFrameIndex();
-  ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(*thread);
-  if (GetWasmModuleID(wasm_thread.GetConcreteFramePC(frame_index)) != 
module_id)
-    return LLDB_INVALID_INDEX32;
+  if (frame) {
+    // A frame can only stand in for the module the stub reports it executing.
+    const uint32_t frame_index = frame->GetConcreteFrameIndex();
+    ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(*thread);
+    if (GetWasmModuleID(wasm_thread.GetConcreteFramePC(frame_index)) ==
+        module_id)
+      return frame_index;
+  }
 
-  return frame_index;
+  return llvm::createStringErrorV(
+      "the Wasm stub can only read a global through a frame, and no frame is "
+      "executing module {0:x}",
+      module_id);
 }
 
 size_t ProcessWasm::ReadMemory(const ProcessAddress &process_addr, void *buf,
@@ -221,7 +239,6 @@ ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind,
     return SendWasmValueQuery(
         llvm::formatv("qWasmStackValue:{0};{1}", frame_index, index).str());
   case eWasmTagGlobal:
-    // A global belongs to a module rather than a frame. See GetWasmGlobal.
     return llvm::createStringError("a Wasm global does not belong to a frame");
   case eWasmTagNotAWasmLocation:
     return llvm::createStringError("not a Wasm location");
@@ -230,30 +247,15 @@ ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds 
kind,
 }
 
 llvm::Expected<lldb::DataBufferSP>
-ProcessWasm::GetWasmGlobal(uint32_t module_id, uint32_t index,
-                           uint32_t frame_index) {
-  // The global index space belongs to a module instance, so an index only 
names
-  // a global together with the instance holding it.
-  if (CanNameInstance(module_id))
-    return SendWasmValueQuery(
-        llvm::formatv("qWasmGlobal:{0};instance:{1};", index, 
module_id).str());
-
-  // A frame stands in for the instance it is executing only where that 
instance
-  // cannot be named.
-  if (frame_index != LLDB_INVALID_INDEX32)
-    return SendWasmValueQuery(
-        llvm::formatv("qWasmGlobal:{0};{1}", frame_index, index).str());
-
-  if (module_id == kWasmInvalidModuleID)
-    return llvm::createStringErrorV(
-        "global {0} belongs to no known module instance, and no frame is "
-        "executing one to read it through",
-        index);
+ProcessWasm::GetWasmGlobalForModule(uint32_t module_id, uint32_t index) {
+  return SendWasmValueQuery(
+      llvm::formatv("qWasmGlobal:{0};instance:{1};", index, module_id).str());
+}
 
-  return llvm::createStringErrorV(
-      "the Wasm stub can only read a global through a frame, and no frame is "
-      "executing module {0:x} to read global {1} through",
-      module_id, index);
+llvm::Expected<lldb::DataBufferSP>
+ProcessWasm::GetWasmGlobalForFrame(uint32_t frame_index, uint32_t index) {
+  return SendWasmValueQuery(
+      llvm::formatv("qWasmGlobal:{0};{1}", frame_index, index).str());
 }
 
 bool ProcessWasm::CanNameInstance(uint32_t module_id) {
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index d4a9a9a2be3bd..cffff2fa54093 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -52,13 +52,24 @@ class ProcessWasm : public 
process_gdb_remote::ProcessGDBRemote {
   GetWasmVariable(WasmVirtualRegisterKinds kind, uint32_t frame_index,
                   uint32_t index);
 
-  /// Query the value of a WebAssembly global. The global index space is per
-  /// module, so an index only names a global together with \a module_id.
-  ///
-  /// \a frame_index only serves a stub that cannot be told which instance to
-  /// read. Pass LLDB_INVALID_INDEX32 when no frame can stand in.
-  llvm::Expected<lldb::DataBufferSP>
-  GetWasmGlobal(uint32_t module_id, uint32_t index, uint32_t frame_index);
+  /// Query the value of the global at \a index in the global index space of 
the
+  /// module instance \a module_id names. An index only names a global together
+  /// with an instance. Only a stub that advertises "qWasmInstance+" can be 
told
+  /// which instance to read, so see CanNameInstance first.
+  llvm::Expected<lldb::DataBufferSP> GetWasmGlobalForModule(uint32_t module_id,
+                                                            uint32_t index);
+
+  /// Query the value of a WebAssembly global through the frame at \a
+  /// frame_index, which reaches only the globals of the module instance that
+  /// frame is executing. This is the only scope a stub that cannot be told
+  /// which instance to read offers.
+  llvm::Expected<lldb::DataBufferSP> GetWasmGlobalForFrame(uint32_t 
frame_index,
+                                                           uint32_t index);
+
+  /// Whether the instance holding a global can be named to the stub, which
+  /// needs both a valid id to name it by and a stub that accepts one. Where it
+  /// cannot, a frame executing that instance has to stand in for it.
+  bool CanNameInstance(uint32_t module_id);
 
 protected:
   std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
@@ -72,20 +83,15 @@ class ProcessWasm : public 
process_gdb_remote::ProcessGDBRemote {
   /// hex-encoded bytes of the whole value.
   llvm::Expected<lldb::DataBufferSP> SendWasmValueQuery(llvm::StringRef 
packet);
 
-  /// Read a WebAssembly global by its index in the global index space of the
-  /// module it belongs to. The index space is per module, so an index only
-  /// names a global together with the module it is an index into.
+  /// Read a WebAssembly global of the module instance \a module_id names,
+  /// through whichever scope that stub can be asked for it in.
   size_t ReadGlobal(uint32_t module_id, uint32_t index, void *buf, size_t size,
                     Status &error);
 
-  /// The frame to read a global of \a module_id through, or
-  /// LLDB_INVALID_INDEX32 when no frame can stand in for that module.
-  uint32_t GetFallbackFrameIndex(uint32_t module_id);
-
-  /// Whether the instance holding a global can be named to the stub, which
-  /// needs both a valid id to name it by and a stub that accepts one. Where it
-  /// cannot, a frame executing that instance has to stand in for it.
-  bool CanNameInstance(uint32_t module_id);
+  /// The frame to read a global of \a module_id through, for a stub that 
cannot
+  /// be told which instance to read. Fails when no frame is executing that
+  /// module, which leaves its globals out of reach.
+  llvm::Expected<uint32_t> GetFallbackFrameIndex(uint32_t module_id);
 
   lldb::DynamicRegisterInfoSP &GetRegisterInfo() { return m_register_info_sp; }
 
diff --git a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp 
b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
index ee50b3c3bdbbc..ba3768a3d5ebc 100644
--- a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
@@ -66,6 +66,21 @@ const RegisterSet 
*RegisterContextWasm::GetRegisterSet(size_t reg_set) {
   return nullptr;
 }
 
+/// A local and an operand stack value belong to the frame, while a global
+/// belongs to the module instance the frame is executing, which is named
+/// directly wherever the stub accepts one.
+static llvm::Expected<DataBufferSP>
+ReadWasmValue(ProcessWasm &process, const WasmVirtualRegisterInfo &reg_info,
+              uint32_t frame_index, uint32_t module_id) {
+  if (reg_info.kind != eWasmTagGlobal)
+    return process.GetWasmVariable(reg_info.kind, frame_index, reg_info.index);
+
+  if (process.CanNameInstance(module_id))
+    return process.GetWasmGlobalForModule(module_id, reg_info.index);
+
+  return process.GetWasmGlobalForFrame(frame_index, reg_info.index);
+}
+
 bool RegisterContextWasm::ReadRegister(const RegisterInfo *reg_info,
                                        RegisterValue &value) {
   ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(GetThread());
@@ -97,11 +112,7 @@ bool RegisterContextWasm::ReadRegister(const RegisterInfo 
*reg_info,
           const_cast<RegisterInfo *>(reg_info));
 
   llvm::Expected<DataBufferSP> maybe_buffer =
-      wasm_reg_info->kind == eWasmTagGlobal
-          ? process->GetWasmGlobal(GetModuleID(), wasm_reg_info->index,
-                                   frame_index)
-          : process->GetWasmVariable(wasm_reg_info->kind, frame_index,
-                                     wasm_reg_info->index);
+      ReadWasmValue(*process, *wasm_reg_info, frame_index, GetModuleID());
   if (!maybe_buffer) {
     LLDB_LOG_ERROR(GetLog(LLDBLog::Process), maybe_buffer.takeError(),
                    "Failed to read Wasm value: {0}");
diff --git a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h 
b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
index ea2cffe811130..dcb6e021435d0 100644
--- a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
+++ b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
@@ -60,11 +60,10 @@ class RegisterContextWasm
                      const RegisterValue &value) override;
 
 private:
-  /// The module whose code this context's frame is executing, which a virtual
-  /// register number has no room to carry. The frame's program counter is 
where
-  /// it comes from. Resolved on each use rather than held onto: the innermost
-  /// frame's context is the thread's own and outlives a stop, so a cached
-  /// answer would go on naming the module of a previous stop.
+  /// The module whose code this context's frame is executing. Resolved on each
+  /// use rather than cached: the innermost frame's register context is the
+  /// thread's own and outlives a stop, so a cached id would go on naming the
+  /// module of a previous stop.
   uint32_t GetModuleID();
 
   std::unordered_map<size_t, std::unique_ptr<WasmVirtualRegisterInfo>>

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to