https://github.com/JDevlieghere created 
https://github.com/llvm/llvm-project/pull/212516

Draft: WIP

>From 829abdfbb277f7433bf90eee143dc06ca178eea1 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Mon, 27 Jul 2026 12:00:00 -0700
Subject: [PATCH 1/2] [lldb] Report the unwound PC for WebAssembly caller
 frames

RegisterContextWasm delegated the PC register read to
GDBRemoteRegisterContext, which reports the live innermost PC for every
frame. Resolving a variable whose DWARF location is a location list in a
caller frame therefore chose the entry using the innermost frame's PC
instead of the caller's, so the variable read back as unavailable even
though its location covered the caller's PC.

Return the program counter the WebAssembly unwinder recorded for the
frame (from qWasmCallStack) when reading a caller frame's PC, so the
location list entry uses the correct frame.
---
 .../Plugins/Process/wasm/RegisterContextWasm.cpp | 16 ++++++++++++++--
 lldb/source/Plugins/Process/wasm/ThreadWasm.cpp  | 10 ++++++++++
 lldb/source/Plugins/Process/wasm/ThreadWasm.h    |  4 ++++
 3 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp 
b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
index bdeb0c927a387..8cbe6571c7c0a 100644
--- a/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
@@ -64,9 +64,21 @@ const RegisterSet 
*RegisterContextWasm::GetRegisterSet(size_t reg_set) {
 
 bool RegisterContextWasm::ReadRegister(const RegisterInfo *reg_info,
                                        RegisterValue &value) {
-  // The only real registers is the PC.
-  if (reg_info->name)
+  // The only real register is the PC.
+  if (reg_info->name) {
+    // A caller frame's PC is the unwound return address, which the base
+    // register context cannot provide because it only sees the innermost
+    // frame's live PC. Use the PC the unwinder recorded for this frame.
+    if (m_concrete_frame_idx > 0) {
+      ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(GetThread());
+      lldb::addr_t pc = wasm_thread.GetConcreteFramePC(m_concrete_frame_idx);
+      if (pc != LLDB_INVALID_ADDRESS) {
+        value.SetUInt(pc, reg_info->byte_size);
+        return true;
+      }
+    }
     return GDBRemoteRegisterContext::ReadRegister(reg_info, value);
+  }
 
   // Read the virtual registers.
   ThreadWasm *thread = static_cast<ThreadWasm *>(&GetThread());
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
index 0666b75d4afe0..c7c05cb815261 100644
--- a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
@@ -12,6 +12,7 @@
 #include "RegisterContextWasm.h"
 #include "UnwindWasm.h"
 #include "lldb/Target/Target.h"
+#include "lldb/Target/Unwind.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -34,6 +35,15 @@ llvm::Expected<std::vector<lldb::addr_t>> 
ThreadWasm::GetWasmCallStack() {
   return llvm::createStringError("no process");
 }
 
+lldb::addr_t ThreadWasm::GetConcreteFramePC(uint32_t concrete_frame_idx) {
+  lldb::addr_t cfa, pc;
+  bool behaves_like_zeroth_frame;
+  if (GetUnwinder().GetFrameInfoAtIndex(concrete_frame_idx, cfa, pc,
+                                        behaves_like_zeroth_frame))
+    return pc;
+  return LLDB_INVALID_ADDRESS;
+}
+
 lldb::RegisterContextSP
 ThreadWasm::CreateRegisterContextForFrame(StackFrame *frame) {
   uint32_t concrete_frame_idx = 0;
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.h 
b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
index c2f5762b30484..a2d1d66ced0d7 100644
--- a/lldb/source/Plugins/Process/wasm/ThreadWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
@@ -25,6 +25,10 @@ class ThreadWasm : public 
process_gdb_remote::ThreadGDBRemote {
   /// Retrieve the current call stack from the WebAssembly remote process.
   llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack();
 
+  /// Return the program counter the Wasm unwinder recorded for the given
+  /// concrete frame index, or LLDB_INVALID_ADDRESS if it is unavailable.
+  lldb::addr_t GetConcreteFramePC(uint32_t concrete_frame_idx);
+
   lldb::RegisterContextSP
   CreateRegisterContextForFrame(StackFrame *frame) override;
 

>From d61be87c73b9b3adc589bfd032f20e526d8ba8b4 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <[email protected]>
Date: Tue, 28 Jul 2026 08:20:38 -0700
Subject: [PATCH 2/2] Wasm Globals WIP

---
 lldb/include/lldb/lldb-enumerations.h         |   1 +
 lldb/source/Core/Section.cpp                  |   3 +
 .../ObjectFile/wasm/ObjectFileWasm.cpp        | 203 +++++++++++++++---
 .../Plugins/ObjectFile/wasm/ObjectFileWasm.h  |  39 ++++
 .../Plugins/Process/wasm/ProcessWasm.cpp      |  35 ++-
 .../source/Plugins/Process/wasm/ProcessWasm.h |   9 +-
 .../gdb_remote_client/TestWasm.py             |  68 ++++++
 .../Shell/ObjectFile/wasm/wasm-globals.yaml   |  99 +++++++++
 8 files changed, 427 insertions(+), 30 deletions(-)
 create mode 100644 lldb/test/Shell/ObjectFile/wasm/wasm-globals.yaml

diff --git a/lldb/include/lldb/lldb-enumerations.h 
b/lldb/include/lldb/lldb-enumerations.h
index 93c252b55de99..0eba26df98b86 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -923,6 +923,7 @@ enum SectionType {
   eSectionTypeLLDBFormatters,
   eSectionTypeSwiftModules,
   eSectionTypeWasmName,
+  eSectionTypeWasmGlobal,
 };
 
 FLAGS_ENUM(EmulateInstructionOptions){
diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp
index f3b01429f61ef..90b561f02db6b 100644
--- a/lldb/source/Core/Section.cpp
+++ b/lldb/source/Core/Section.cpp
@@ -153,6 +153,8 @@ const char *Section::GetTypeAsCString() const {
     return "swift-modules";
   case eSectionTypeWasmName:
     return "wasm-name";
+  case eSectionTypeWasmGlobal:
+    return "wasm-global";
   case eSectionTypeOther:
     return "regular";
   }
@@ -410,6 +412,7 @@ bool Section::ContainsOnlyDebugInfo() const {
   case eSectionTypeGoSymtab:
   case eSectionTypeAbsoluteAddress:
   case eSectionTypeWasmName:
+  case eSectionTypeWasmGlobal:
   case eSectionTypeOther:
   // Used for "__dof_cache" in mach-o or ".debug" for COFF which isn't debug
   // information that we parse at all. This was causing system files with no
diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp 
b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
index 644e6e3f03081..ccf7fd1d565a1 100644
--- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
+++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
@@ -322,10 +322,14 @@ struct WasmFunction {
   uint32_t code_offset = 0;
 };
 
-static llvm::Expected<uint32_t> ParseImports(DataExtractor &import_data) {
-  // Currently this function just returns the number of imported functions.
-  // If we want to do anything with global names in the future, we'll also
-  // need to know those.
+/// The number of imports of each kind. Imports occupy the low indices of the
+/// index space of their kind.
+struct WasmImports {
+  uint32_t functions = 0;
+  uint32_t globals = 0;
+};
+
+static llvm::Expected<WasmImports> ParseImports(DataExtractor &import_data) {
   llvm::DataExtractor data = import_data.GetAsLLVM();
   llvm::DataExtractor::Cursor c(0);
 
@@ -333,7 +337,7 @@ static llvm::Expected<uint32_t> ParseImports(DataExtractor 
&import_data) {
   if (!count)
     return count.takeError();
 
-  uint32_t function_imports = 0;
+  WasmImports imports;
   for (uint32_t i = 0; c && i < *count; ++i) {
     // We don't need module and field names, so we can just get them as raw
     // strings and discard.
@@ -348,19 +352,39 @@ static llvm::Expected<uint32_t> 
ParseImports(DataExtractor &import_data) {
           llvm::createStringError("failed to parse field name"),
           field_name.takeError());
 
-    uint8_t kind = data.getU8(c);
-    if (kind == llvm::wasm::WASM_EXTERNAL_FUNCTION)
-      function_imports++;
-
-    // For function imports, this is a type index. For others it's different.
-    // We don't need it, just need to parse it to advance the cursor.
-    data.getULEB128(c);
+    // The descriptor differs per kind, so each has to be parsed to find where
+    // the next import starts.
+    const uint8_t kind = data.getU8(c);
+    switch (kind) {
+    case llvm::wasm::WASM_EXTERNAL_FUNCTION:
+      imports.functions++;
+      data.getULEB128(c); // type index
+      break;
+    case llvm::wasm::WASM_EXTERNAL_GLOBAL:
+      imports.globals++;
+      data.getU8(c); // value type
+      data.getU8(c); // mutability
+      break;
+    case llvm::wasm::WASM_EXTERNAL_TABLE:
+      data.getU8(c); // element type
+      LLVM_FALLTHROUGH;
+    case llvm::wasm::WASM_EXTERNAL_MEMORY: {
+      // Tables and memories are both described by limits.
+      const uint8_t flags = data.getU8(c);
+      data.getULEB128(c); // minimum
+      if (flags & llvm::wasm::WASM_LIMITS_FLAG_HAS_MAX)
+        data.getULEB128(c);
+      break;
+    }
+    default:
+      return llvm::createStringError("unknown import kind %u", kind);
+    }
   }
 
   if (!c)
     return c.takeError();
 
-  return function_imports;
+  return imports;
 }
 
 /// Get the offset in the function to the first instruction.
@@ -509,11 +533,50 @@ static llvm::Expected<uint64_t> 
ParseMemoryMinSize(DataExtractor &data) {
   return static_cast<uint64_t>(*min_pages) * llvm::wasm::WasmDefaultPageSize;
 }
 
+/// Size in bytes of a WebAssembly value type, or zero for the types whose
+/// values cannot be read.
+static uint32_t GetWasmValueTypeSize(uint8_t type) {
+  switch (type) {
+  case llvm::wasm::WASM_TYPE_I32:
+  case llvm::wasm::WASM_TYPE_F32:
+    return 4;
+  case llvm::wasm::WASM_TYPE_I64:
+  case llvm::wasm::WASM_TYPE_F64:
+    return 8;
+  default:
+    return 0;
+  }
+}
+
+/// Parse the module's own globals, which start at the number of imported ones.
+static llvm::Expected<std::vector<WasmGlobal>>
+ParseGlobals(DataExtractor &data) {
+  lldb::offset_t offset = 0;
+
+  llvm::Expected<uint32_t> count = GetULEB32(data, offset);
+  if (!count)
+    return count.takeError();
+
+  std::vector<WasmGlobal> globals;
+  globals.reserve(*count);
+
+  for (uint32_t i = 0; i < *count; ++i) {
+    WasmGlobal global;
+    global.size = GetWasmValueTypeSize(data.GetU8(&offset));
+    global.mutability = data.GetU8(&offset) != 0;
+    global.init_expr_value = GetWasmOffsetFromInitExpr(data, offset);
+    globals.push_back(global);
+  }
+
+  return globals;
+}
+
 static llvm::Expected<std::vector<Symbol>>
-ParseNames(SectionSP code_section_sp, DataExtractor &name_data,
-           const std::vector<WasmFunction> &functions,
+ParseNames(SectionSP code_section_sp, SectionSP global_section_sp,
+           DataExtractor &name_data, const std::vector<WasmFunction> 
&functions,
            std::vector<WasmSegment> &segments,
-           uint32_t num_imported_functions) {
+           const std::vector<WasmGlobal> &globals,
+           uint32_t num_imported_functions, uint32_t num_imported_globals) {
 
   llvm::DataExtractor data = name_data.GetAsLLVM();
   llvm::DataExtractor::Cursor c(0);
@@ -582,7 +645,36 @@ ParseNames(SectionSP code_section_sp, DataExtractor 
&name_data,
       }
 
     } break;
-    case llvm::wasm::WASM_NAMES_GLOBAL:
+    case llvm::wasm::WASM_NAMES_GLOBAL: {
+      llvm::Expected<uint32_t> count = GetULEB32(data, c);
+      if (!count)
+        return count.takeError();
+      for (uint32_t i = 0; c && i < *count; ++i) {
+        llvm::Expected<uint32_t> idx = GetULEB32(data, c);
+        if (!idx)
+          return idx.takeError();
+        llvm::Expected<std::string> name = GetWasmString(data, c);
+        if (!name)
+          return name.takeError();
+
+        // An imported global has no entry in the global section, and so no
+        // size to bound a read with.
+        if (*idx < num_imported_globals)
+          continue;
+        const uint32_t global_idx = *idx - num_imported_globals;
+        if (global_idx >= globals.size())
+          continue;
+
+        symbols.emplace_back(symbols.size(), *name, lldb::eSymbolTypeData,
+                             /*external=*/true, /*is_debug=*/false,
+                             /*is_trampoline=*/false, /*is_artificial=*/false,
+                             global_section_sp, /*offset=*/*idx,
+                             globals[global_idx].size,
+                             /*size_is_valid=*/true,
+                             /*contains_linker_annotations=*/false,
+                             /*flags=*/0);
+      }
+    } break;
     case llvm::wasm::WASM_NAMES_LOCAL:
     default:
       std::optional<lldb::offset_t> offset =
@@ -718,17 +810,31 @@ void ObjectFileWasm::CreateSections(SectionList 
&unified_section_list) {
     }
   }
 
-  // Parse the import section. The number of functions is needed because the
-  // function index space used in the name section includes imports.
+  // Parse the import section. The counts are needed because the function and
+  // global index spaces used in the name section include imports.
   if (std::optional<section_info> info =
           GetSectionInfo(llvm::wasm::WASM_SEC_IMPORT)) {
     DataExtractor import_data = ReadImageData(info->offset, info->size);
-    llvm::Expected<uint32_t> num_imports = ParseImports(import_data);
-    if (!num_imports) {
-      LLDB_LOG_ERROR(log, num_imports.takeError(),
+    llvm::Expected<WasmImports> imports = ParseImports(import_data);
+    if (!imports) {
+      LLDB_LOG_ERROR(log, imports.takeError(),
                      "Failed to parse Wasm import section: {0}");
     } else {
-      m_num_imported_functions = *num_imports;
+      m_num_imported_functions = imports->functions;
+      m_num_imported_globals = imports->globals;
+    }
+  }
+
+  // Parse the global section.
+  if (std::optional<section_info> info =
+          GetSectionInfo(llvm::wasm::WASM_SEC_GLOBAL)) {
+    DataExtractor global_data = ReadImageData(info->offset, info->size);
+    llvm::Expected<std::vector<WasmGlobal>> globals = 
ParseGlobals(global_data);
+    if (!globals) {
+      LLDB_LOG_ERROR(log, globals.takeError(),
+                     "Failed to parse Wasm global section: {0}");
+    } else {
+      m_globals = *globals;
     }
   }
 
@@ -747,11 +853,28 @@ void ObjectFileWasm::CreateSections(SectionList 
&unified_section_list) {
     }
   }
 
+  // There is nothing to map: the section exists to give globals a load address
+  // in the Global space, which is what lets one be looked up by name and read.
+  SectionSP global_section_sp;
+  if (!m_globals.empty()) {
+    global_section_sp = std::make_shared<Section>(
+        GetModule(),
+        /*obj_file=*/this, eSectionTypeWasmGlobal, ConstString("global"),
+        eSectionTypeWasmGlobal,
+        /*file_vm_addr=*/0,
+        /*vm_size=*/m_num_imported_globals + m_globals.size(),
+        /*file_offset=*/0, /*file_size=*/0,
+        /*log2align=*/0, /*flags=*/0);
+    m_sections_up->AddSection(global_section_sp);
+    unified_section_list.AddSection(global_section_sp);
+  }
+
   if (std::optional<section_info> info = GetSectionInfo("name")) {
     DataExtractor names_data = ReadImageData(info->offset, info->size);
     llvm::Expected<std::vector<Symbol>> symbols = ParseNames(
         m_sections_up->FindSectionByType(lldb::eSectionTypeCode, false),
-        names_data, functions, segments, m_num_imported_functions);
+        global_section_sp, names_data, functions, segments, m_globals,
+        m_num_imported_functions, m_num_imported_globals);
     if (!symbols) {
       LLDB_LOG_ERROR(log, symbols.takeError(),
                      "Failed to parse Wasm names: {0}");
@@ -832,6 +955,33 @@ void ObjectFileWasm::CreateSections(SectionList 
&unified_section_list) {
   }
 }
 
+size_t ObjectFileWasm::ReadSectionData(Section *section,
+                                       lldb::offset_t section_offset, void 
*dst,
+                                       size_t dst_len) {
+  if (!section || section->GetType() != eSectionTypeWasmGlobal)
+    return ObjectFile::ReadSectionData(section, section_offset, dst, dst_len);
+
+  // The low indices belong to imported globals, which the module does not
+  // declare and so has no initializer for.
+  if (section_offset < m_num_imported_globals)
+    return 0;
+  const lldb::offset_t index = section_offset - m_num_imported_globals;
+  if (index >= m_globals.size())
+    return 0;
+
+  const WasmGlobal &global = m_globals[index];
+  if (global.init_expr_value == LLDB_INVALID_OFFSET || global.size == 0 ||
+      dst_len > global.size)
+    return 0;
+
+  // A global holds a value, not bytes, so give it the module's byte order.
+  const uint64_t value = global.init_expr_value;
+  uint8_t bytes[sizeof(value)];
+  llvm::support::endian::write64le(bytes, value);
+  std::memcpy(dst, bytes, dst_len);
+  return dst_len;
+}
+
 bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address,
                                     bool value_is_offset) {
   /// In WebAssembly, linear memory is disjointed from code space. The VM can
@@ -880,6 +1030,11 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, 
lldb::addr_t load_address,
       section_load_addr = (load_address & ~(uint64_t(0b11) << 62)) |
                           section_sp->GetFileAddress();
       break;
+    case eSectionTypeWasmGlobal:
+      // The offset is an index into the global index space, not a byte offset.
+      section_load_addr = (load_address & ~(uint64_t(0b11) << 62)) |
+                          (uint64_t(WasmAddressType::Global) << 62);
+      break;
     default:
       // Code (and other) sections are addressed by their offset within the
       // module in the Object address space.
diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h 
b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h
index 588617fb16251..7db482b0ba650 100644
--- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h
+++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h
@@ -16,6 +16,38 @@
 namespace lldb_private {
 namespace wasm {
 
+/// Each WebAssembly module has separate 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.
+///
+/// Globals are not addressable: they live in an index space of their own. The
+/// synthetic Global space stands in for one, holding the index where an 
address
+/// would hold an offset, so that a global can be named and read.
+///
+/// 0x03 is deliberately left unused. It is how an address outside of any known
+/// space is reported, so reusing it would make a global indistinguishable from
+/// a rejected address.
+enum WasmAddressType : uint8_t {
+  Memory = 0x00,
+  Object = 0x01,
+  Global = 0x02,
+  Invalid = 0xff
+};
+
+/// A global declared by the module itself. Imported globals occupy the low
+/// indices of the same index space and have no entry here.
+struct WasmGlobal {
+  /// Size of the global's value type, which bounds what a read can produce.
+  uint32_t size = 0;
+
+  /// Whether the global can be written to after instantiation.
+  bool mutability = false;
+
+  /// Value the global is initialized with, or LLDB_INVALID_OFFSET when the
+  /// initializer is not a simple constant.
+  lldb::offset_t init_expr_value = LLDB_INVALID_OFFSET;
+};
+
 /// Generic Wasm object file reader.
 ///
 /// This class provides a generic wasm32 reader plugin implementing the
@@ -100,6 +132,11 @@ class ObjectFileWasm : public ObjectFile {
   bool SetLoadAddress(lldb_private::Target &target, lldb::addr_t value,
                       bool value_is_offset) override;
 
+  /// A global has no bytes in the module to read, so serve its initial value
+  /// when there is no process to ask for the current one.
+  size_t ReadSectionData(Section *section, lldb::offset_t section_offset,
+                         void *dst, size_t dst_len) override;
+
   lldb_private::Address GetBaseAddress() override {
     return IsInMemory() ? Address(m_memory_addr) : Address(0);
   }
@@ -148,6 +185,8 @@ class ObjectFileWasm : public ObjectFile {
 
   std::vector<section_info> m_sect_infos;
   uint32_t m_num_imported_functions = 0;
+  uint32_t m_num_imported_globals = 0;
+  std::vector<WasmGlobal> m_globals;
   std::vector<Symbol> m_symbols;
   ArchSpec m_arch;
   UUID m_uuid;
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index b378a772b324f..a13cc38faa625 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -84,6 +84,37 @@ std::shared_ptr<ThreadGDBRemote> 
ProcessWasm::CreateThread(lldb::tid_t tid) {
   return std::make_shared<ThreadWasm>(*this, tid);
 }
 
+size_t ProcessWasm::ReadGlobal(uint32_t index, void *buf, size_t size,
+                               Status &error) {
+  // A global is resolved against a frame, so there has to be one.
+  Thread *thread = GetThreadList().GetSelectedThread().get();
+  if (!thread) {
+    error = Status::FromErrorString(
+        "Wasm global read failed: no thread to read the global from");
+    return 0;
+  }
+
+  llvm::Expected<lldb::DataBufferSP> buffer =
+      GetWasmVariable(eWasmTagGlobal, /*frame_index=*/0, index);
+  if (!buffer) {
+    error = Status::FromError(buffer.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();
+  if (size > global_size) {
+    error = Status::FromErrorStringWithFormatv(
+        "Wasm global read failed: requested {0} bytes from a {1}-byte global",
+        size, global_size);
+    return 0;
+  }
+
+  std::memcpy(buf, (*buffer)->GetBytes(), size);
+  return size;
+}
+
 size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
                                Status &error) {
   wasm_addr_t wasm_addr(vm_addr);
@@ -92,11 +123,13 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void 
*buf, size_t size,
   case WasmAddressType::Memory:
   case WasmAddressType::Object:
     return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+  case WasmAddressType::Global:
+    return ReadGlobal(wasm_addr.GetOffset(), buf, size, error);
   case WasmAddressType::Invalid:
     break;
   }
 
-  error.FromErrorStringWithFormatv(
+  error = Status::FromErrorStringWithFormatv(
       "Wasm read failed for invalid address {0:x} (type = {1:x}, module = "
       "{2:x}, offset = {3:x})",
       vm_addr, wasm_addr.GetType(), wasm_addr.GetModuleID(),
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index f9744e091ce16..e0bc7f519bfdb 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -9,17 +9,13 @@
 #ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
 #define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
 
+#include "Plugins/ObjectFile/wasm/ObjectFileWasm.h"
 #include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
 #include "Utility/WasmVirtualRegisters.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.
@@ -87,6 +83,9 @@ class ProcessWasm : public 
process_gdb_remote::ProcessGDBRemote {
   friend class UnwindWasm;
   friend class ThreadWasm;
 
+  /// Read a WebAssembly global by its index in the global index space.
+  size_t ReadGlobal(uint32_t index, void *buf, size_t size, Status &error);
+
   lldb::DynamicRegisterInfoSP &GetRegisterInfo() { return m_register_info_sp; }
 
   ProcessWasm(const ProcessWasm &);
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py 
b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index a6c0dc9286b0c..88a6fd4d78d69 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -10,6 +10,13 @@
 LOAD_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
+
+# Globals the fake engine holds, as index -> (size in bytes, value).
+WASM_GLOBALS = {0: (4, 0x2A), 1: (8, 0xDEADBEEF)}
+
 
 def format_register_value(val):
     """
@@ -88,8 +95,21 @@ def respond(self, packet):
             return self.qWasmCallStack()
         if packet.startswith("qWasmLocal"):
             return self.qWasmLocal(packet)
+        if packet.startswith("qWasmGlobal"):
+            return self.qWasmGlobal(packet)
         return MockGDBServerResponder.respond(self, packet)
 
+    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()
+
     def qSupported(self, client_supported):
         return "qXfer:libraries:read+;PacketSize=1000;vContSupported-"
 
@@ -385,3 +405,51 @@ def test_simple_wasm_debugging_session(self):
         b = frame0.FindVariable("b")
         self.assertTrue(b.IsValid())
         self.assertEqual(b.GetValueAsUnsigned(), 2)
+
+    @skipIfAsan
+    @skipIfXmlSupportMissing
+    def test_read_global(self):
+        """Test that a WebAssembly global can be read through its synthetic
+        address, 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)
+        self.yaml2obj(yaml_path, obj_path)
+
+        call_stacks = [WasmCallStack([WasmStackFrame(0x019C)])]
+        self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks)
+
+        target = self.dbg.CreateTarget("")
+        process = self.connect(target, "wasm")
+        lldbutil.expect_state_changes(
+            self, self.dbg.GetListener(), process, [lldb.eStateStopped]
+        )
+
+        # A global is read as a whole value.
+        error = lldb.SBError()
+        data = process.ReadMemory(WASM_GLOBAL_ADDRESS | 0, 4, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x2A)
+
+        data = process.ReadMemory(WASM_GLOBAL_ADDRESS | 1, 8, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0xDEADBEEF)
+
+        # 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(WASM_GLOBAL_ADDRESS | 0, 1, error)
+        self.assertSuccess(error)
+        self.assertEqual(int.from_bytes(data, "little"), 0x2A)
+
+        # Reading more than a global holds would have to come from somewhere
+        # else. The next index is not adjacent storage, so this fails rather
+        # than returning whatever is nearby.
+        process.ReadMemory(WASM_GLOBAL_ADDRESS | 0, 8, error)
+        self.assertFalse(error.Success())
+        self.assertIn("4-byte global", error.GetCString())
+
+        # Likewise for a global that does not exist.
+        process.ReadMemory(WASM_GLOBAL_ADDRESS | 99, 4, error)
+        self.assertFalse(error.Success())
diff --git a/lldb/test/Shell/ObjectFile/wasm/wasm-globals.yaml 
b/lldb/test/Shell/ObjectFile/wasm/wasm-globals.yaml
new file mode 100644
index 0000000000000..bc371893cb184
--- /dev/null
+++ b/lldb/test/Shell/ObjectFile/wasm/wasm-globals.yaml
@@ -0,0 +1,99 @@
+# REQUIRES: webassembly
+
+# WebAssembly globals live in an index space of their own and hold values 
rather
+# than bytes in the module, so they have no address to be named or read by. 
Give
+# them a synthetic address space in which the offset is the global index, and
+# serve the initializer when there is no process to ask for the current value.
+
+# RUN: yaml2obj %s -o %t.wasm
+# RUN: %lldb %t.wasm \
+# RUN:   -o "target modules dump sections" \
+# RUN:   -o "target modules dump symtab" \
+# RUN:   -o "target modules load --file %t.wasm --slide 0x4000000000000000" \
+# RUN:   -o "target modules dump sections" \
+# RUN:   -o "image lookup -s g_answer" \
+# RUN:   -o "image lookup -s g_wide" \
+# RUN:   -o "image lookup -s g_imported" \
+# RUN:   -o exit 2>&1 | FileCheck %s
+
+# The global section spans the whole index space, imported globals included.
+# CHECK: Dumping sections
+# CHECK: wasm-global {{.*}}[0x0000000000000000-0x0000000000000003)
+
+# Each named global becomes a data symbol at its index, sized by its value type
+# rather than by the type the debug info gives it.
+# CHECK: Dumping symbol table
+# CHECK: Data 0x0000000000000001 0x0000000000000004 {{.*}}g_answer
+# CHECK: Data 0x0000000000000002 0x0000000000000008 {{.*}}g_wide
+
+# Loading the module puts the globals in their own space (tag 0b10), not in the
+# Object space the code is mapped into.
+# CHECK: Dumping sections
+# CHECK: wasm-global {{.*}}[0x8000000000000000-0x8000000000000003)
+
+# A global resolves to its index within that space.
+# CHECK: 1 symbols match 'g_answer'
+# CHECK: (wasm-globals.yaml.tmp.wasm.global + 1)
+# CHECK: 1 symbols match 'g_wide'
+# CHECK: (wasm-globals.yaml.tmp.wasm.global + 2)
+
+# An imported global is declared by another module, so this one has neither a
+# value type to size it nor an initializer to read, and gets no symbol.
+# CHECK-NOT: symbols match 'g_imported'
+
+--- !WASM
+FileHeader:
+  Version: 0x1
+Sections:
+  - Type: TYPE
+    Signatures:
+      - Index: 0
+        ParamTypes: []
+        ReturnTypes:
+          - I32
+  - Type: IMPORT
+    Imports:
+    # An imported global occupies index 0, so the module's own globals start at
+    # index 1. It has no initializer here to read back.
+      - Module: env
+        Field: g_imported
+        Kind: GLOBAL
+        GlobalType: I32
+        GlobalMutable: false
+  - Type: FUNCTION
+    FunctionTypes: [ 0 ]
+  - Type: MEMORY
+    Memories:
+      - Minimum: 0x1
+  - Type: GLOBAL
+    Globals:
+      - Index: 1
+        Type: I32
+        Mutable: false
+        InitExpr:
+          Opcode: I32_CONST
+          Value: 42
+      - Index: 2
+        Type: I64
+        Mutable: true
+        InitExpr:
+          Opcode: I64_CONST
+          Value: 3735928559
+  - Type: CODE
+    Functions:
+      - Index: 0
+        Locals: []
+        Body: 412A0F0B
+  - Type: CUSTOM
+    Name: name
+    FunctionNames:
+      - Index: 0
+        Name: get_42
+    GlobalNames:
+      - Index: 0
+        Name: g_imported
+      - Index: 1
+        Name: g_answer
+      - Index: 2
+        Name: g_wide
+...

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

Reply via email to