https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/212516
>From 11288e8a9cf3b76c6422d45676df9270dadf07fc Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Tue, 28 Jul 2026 11:10:05 -0700 Subject: [PATCH 1/4] [lldb] Read WebAssembly globals WebAssembly globals are not addressable: they live in an index space of their own and hold values rather than bytes in the module, so nothing in LLDB could name or read one. Give them a synthetic address space in which an address holds the global index where it would otherwise hold an offset, and a section spanning that index space so a global becomes a symbol. With a process, a read of such an address asks the engine over qWasmGlobal. Without one, the object file serves the initializer, which is all that is known before the module is instantiated. The section is based in a range of its own rather than at zero. Code and linear memory are both addressed from zero, so a global index would otherwise name a code or data address as well. The import section now parses each import descriptor by kind, because the global index space includes imported globals and finding the next import means knowing the shape of the current one. WasmAddressType and wasm_addr_t move to a header of their own, shared by the object file that mints these addresses and the process that decodes them. Reading an address in no known space also reports the error it used to build and drop. --- lldb/include/lldb/lldb-enumerations.h | 1 + lldb/source/Core/Section.cpp | 3 + .../ObjectFile/Mach-O/ObjectFileMachO.cpp | 1 + .../ObjectFile/wasm/ObjectFileWasm.cpp | 283 +++++++++++++++--- .../Plugins/ObjectFile/wasm/ObjectFileWasm.h | 21 ++ .../Plugins/ObjectFile/wasm/WasmAddress.h | 67 +++++ .../Plugins/Process/wasm/ProcessWasm.cpp | 39 ++- .../source/Plugins/Process/wasm/ProcessWasm.h | 33 +- lldb/source/Symbol/ObjectFile.cpp | 1 + .../gdb_remote_client/TestWasm.py | 77 +++++ .../Shell/ObjectFile/wasm/wasm-globals.yaml | 162 ++++++++++ 11 files changed, 623 insertions(+), 65 deletions(-) create mode 100644 lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h 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/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp index 9eab45f57f422..6e49f211326ff 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -1165,6 +1165,7 @@ AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) { case eSectionTypeDataObjCCFStrings: case eSectionTypeGoSymtab: case eSectionTypeWasmName: + case eSectionTypeWasmGlobal: return AddressClass::eData; case eSectionTypeDebug: diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp index 644e6e3f03081..3f73e239afe47 100644 --- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp @@ -26,6 +26,7 @@ #include "llvm/Support/CheckedArithmetic.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Format.h" +#include <cstring> #include <optional> using namespace lldb; @@ -37,6 +38,12 @@ LLDB_PLUGIN_DEFINE(ObjectFileWasm) static const uint32_t kWasmHeaderSize = sizeof(llvm::wasm::WasmMagic) + sizeof(llvm::wasm::WasmVersion); +/// File address the synthetic global section is based at. Code and linear +/// memory are both addressed from zero, so the globals need a range of their +/// own to keep a global index from naming a code or data address as well. +static constexpr lldb::addr_t kWasmGlobalFileAddress = + uint64_t(WasmAddressType::Global) << kWasmAddressTypeShift; + /// Helper to read a 32-bit ULEB using LLDB's DataExtractor. static inline llvm::Expected<uint32_t> GetULEB32(DataExtractor &data, lldb::offset_t &offset) { @@ -106,10 +113,10 @@ static lldb::offset_t GetWasmOffsetFromInitExpr(DataExtractor &data, return init_expr_offset; // Extended init expressions are not supported, but we still have to parse - // them to skip over them and read the next segment. - do { + // them to skip over them and read the next segment. A truncated expression + // never reaches the end opcode, so the scan is bounded by the data. + while (opcode != llvm::wasm::WASM_OPCODE_END && data.ValidOffset(offset)) opcode = data.GetU8(&offset); - } while (opcode != llvm::wasm::WASM_OPCODE_END); return LLDB_INVALID_OFFSET; } @@ -322,10 +329,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 +344,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 +359,46 @@ 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_TAG: + data.getU8(c); // attribute + data.getULEB128(c); // type index + break; + case llvm::wasm::WASM_EXTERNAL_TABLE: + data.getU8(c); // element type + [[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: + // The cursor's error has to be consumed before it goes out of scope. + return llvm::joinErrors( + c.takeError(), + 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 +547,93 @@ 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 nothing for the types whose +/// values cannot be read. +static std::optional<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 std::nullopt; + } +} + +/// Parse the init expr that gives a global its initial value. The result is the +/// bit pattern of the value, so an operand that has to be evaluated against +/// module state yields nothing. +static std::optional<uint64_t> ParseGlobalInitValue(DataExtractor &data, + lldb::offset_t &offset) { + std::optional<uint64_t> value; + + switch (data.GetU8(&offset)) { + case llvm::wasm::WASM_OPCODE_I32_CONST: + value = static_cast<uint32_t>(data.GetSLEB128(&offset)); + break; + case llvm::wasm::WASM_OPCODE_I64_CONST: + value = static_cast<uint64_t>(data.GetSLEB128(&offset)); + break; + case llvm::wasm::WASM_OPCODE_F32_CONST: + value = data.GetU32(&offset); + break; + case llvm::wasm::WASM_OPCODE_F64_CONST: + value = data.GetU64(&offset); + break; + case llvm::wasm::WASM_OPCODE_GLOBAL_GET: + case llvm::wasm::WASM_OPCODE_REF_NULL: + // The operand still has to be consumed to find the end of the expression. + data.GetULEB128(&offset); + break; + } + + // An expression this parser does not understand can only be skipped to its + // end opcode. If that end never comes the parse is out of step, and there is + // no value to report. + uint8_t opcode = data.GetU8(&offset); + while (opcode != llvm::wasm::WASM_OPCODE_END && data.ValidOffset(offset)) + opcode = data.GetU8(&offset); + if (opcode != llvm::wasm::WASM_OPCODE_END) + return std::nullopt; + + return value; +} + +/// 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(); + + // The count comes from the file, so it is not a size to allocate up front. + std::vector<WasmGlobal> globals; + + for (uint32_t i = 0; i < *count; ++i) { + if (!data.ValidOffset(offset)) + return llvm::createStringError( + "global section holds %zu of its %u globals", globals.size(), *count); + + WasmGlobal global; + global.size = GetWasmValueTypeSize(data.GetU8(&offset)); + data.GetU8(&offset); // mutability + global.init_expr_value = ParseGlobalInitValue(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); @@ -528,7 +648,9 @@ ParseNames(SectionSP code_section_sp, DataExtractor &name_data, case llvm::wasm::WASM_NAMES_FUNCTION: { const uint64_t count = data.getULEB128(c); if (count > std::numeric_limits<uint32_t>::max()) - return llvm::createStringError("function count overflows uint32_t"); + return llvm::joinErrors( + c.takeError(), + llvm::createStringError("function count overflows uint32_t")); for (uint64_t i = 0; c && i < count; ++i) { llvm::Expected<uint32_t> idx = GetULEB32(data, c); @@ -582,13 +704,46 @@ 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 + // value 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; + + // The section is indexed rather than byte addressed, so a global spans + // the one index it occupies. Bounding a read by the size of the value + // is the section's business. + symbols.emplace_back(symbols.size(), *name, lldb::eSymbolTypeData, + /*external=*/true, /*is_debug=*/false, + /*is_trampoline=*/false, /*is_artificial=*/false, + global_section_sp, /*offset=*/*idx, + /*size=*/1, + /*size_is_valid=*/true, + /*contains_linker_annotations=*/false, + /*flags=*/0); + } + } break; case llvm::wasm::WASM_NAMES_LOCAL: default: std::optional<lldb::offset_t> offset = llvm::checkedAddUnsigned<lldb::offset_t>(c.tell(), *size); if (!offset) - return llvm::createStringError("offset overflows 64 bits"); + return llvm::joinErrors( + c.takeError(), llvm::createStringError("offset overflows 64 bits")); c.seek(*offset); } } @@ -718,17 +873,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 +916,29 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) { } } + // The section maps nothing: it exists to give globals an address, which is + // what lets one be named and read. Its size counts globals rather than bytes, + // imported ones included, because they share the index space. + 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=*/kWasmGlobalFileAddress, + /*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 +1019,31 @@ 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 || !global.size || dst_len > *global.size) + return 0; + + // A global holds a value rather than bytes, and WebAssembly is little-endian. + uint8_t bytes[sizeof(uint64_t)]; + llvm::support::endian::write64le(bytes, *global.init_expr_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 @@ -873,12 +1085,13 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address, case eSectionTypeZeroFill: case eSectionTypeLLDBTypeSummaries: case eSectionTypeLLDBFormatters: - // These all live in linear memory, a separate address space from code - // (the top two bits of the 64-bit address encode the space: 0 for Memory, - // 1 for Object/code), so place the section at its virtual address in the - // Memory space while preserving the module id. - section_load_addr = (load_address & ~(uint64_t(0b11) << 62)) | - section_sp->GetFileAddress(); + case eSectionTypeWasmGlobal: + // These live in linear memory, and the globals in an index space of their + // own, both separate from code. A section's file address already carries + // the space it belongs to, so only the module id comes from the load + // address. + section_load_addr = + (load_address & ~kWasmAddressTypeMask) | section_sp->GetFileAddress(); break; default: // Code (and other) sections are addressed by their offset within the diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h index 588617fb16251..80a955211ebb2 100644 --- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h +++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.h @@ -9,6 +9,7 @@ #ifndef LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_OBJECTFILEWASM_H #define LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_OBJECTFILEWASM_H +#include "WasmAddress.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Utility/ArchSpec.h" #include <optional> @@ -16,6 +17,19 @@ namespace lldb_private { namespace wasm { +/// 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. + /// Absent for a value type whose values cannot be read. + std::optional<uint32_t> size; + + /// Value the global is initialized with, when the initializer is a constant. + /// Anything else has to be evaluated against module state the object file + /// does not have. + std::optional<uint64_t> init_expr_value; +}; + /// Generic Wasm object file reader. /// /// This class provides a generic wasm32 reader plugin implementing the @@ -100,6 +114,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 +167,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/ObjectFile/wasm/WasmAddress.h b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h new file mode 100644 index 0000000000000..2535f4580377a --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_WASMADDRESS_H +#define LLDB_SOURCE_PLUGINS_OBJECTFILE_WASM_WASMADDRESS_H + +#include "lldb/lldb-types.h" +#include <cstdint> + +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. +/// +/// The tag is two bits wide and these are the only spaces there are, so the +/// remaining value means an address that belongs to nothing. +enum WasmAddressType : uint8_t { + Memory = 0x00, + Object = 0x01, + Global = 0x02, + Invalid = 0x03, +}; + +/// The top two bits of a 64-bit address hold the space it belongs to. +static constexpr uint32_t kWasmAddressTypeShift = 62; +static constexpr uint64_t kWasmAddressTypeMask = uint64_t(0b11) + << kWasmAddressTypeShift; + +/// For the purpose of debugging, we can represent all these separated 32-bit +/// address spaces with a single virtual 64-bit address space. The +/// wasm_addr_t provides this encoding using bitfields. +struct wasm_addr_t { + uint64_t offset : 32; + uint64_t module_id : 30; + uint64_t type : 2; + + wasm_addr_t(lldb::addr_t addr) + : offset(addr & 0x00000000ffffffff), + module_id((addr & 0x00ffffff00000000) >> 32), type(addr >> 62) {} + + wasm_addr_t(WasmAddressType type, uint32_t module_id, uint32_t offset) + : offset(offset), module_id(module_id), type(type) {} + + WasmAddressType GetType() const { return static_cast<WasmAddressType>(type); } + uint32_t GetModuleID() const { return module_id; } + uint32_t GetOffset() const { return offset; } + + operator lldb::addr_t() { return *(uint64_t *)this; } +}; + +static_assert(sizeof(wasm_addr_t) == 8, ""); + +} // namespace wasm +} // namespace lldb_private + +#endif diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp index b378a772b324f..4316d1aa1f6b2 100644 --- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp +++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp @@ -11,7 +11,9 @@ #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Value.h" +#include "lldb/Target/StackFrame.h" #include "lldb/Utility/DataBufferHeap.h" +#include <cstring> #include "lldb/Target/UnixSignals.h" @@ -84,6 +86,39 @@ 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) { + // The protocol asks for a global relative to a frame, so a read needs one. + ThreadSP thread = GetThreadList().GetSelectedThread(); + StackFrameSP frame = + thread ? thread->GetSelectedFrame(DoNoSelectMostRelevantFrame) : nullptr; + if (!frame) { + error = Status::FromErrorString( + "Wasm global read failed: no frame to read the global from"); + return 0; + } + + llvm::Expected<lldb::DataBufferSP> buffer = + GetWasmVariable(eWasmTagGlobal, frame->GetConcreteFrameIndex(), 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 +127,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..44cbd6b16efcd 100644 --- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h +++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h @@ -9,41 +9,13 @@ #ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H #define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H +#include "Plugins/ObjectFile/wasm/WasmAddress.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. -struct wasm_addr_t { - uint64_t offset : 32; - uint64_t module_id : 30; - uint64_t type : 2; - - wasm_addr_t(lldb::addr_t addr) - : offset(addr & 0x00000000ffffffff), - module_id((addr & 0x00ffffff00000000) >> 32), type(addr >> 62) {} - - wasm_addr_t(WasmAddressType type, uint32_t module_id, uint32_t offset) - : offset(offset), module_id(module_id), type(type) {} - - WasmAddressType GetType() const { return static_cast<WasmAddressType>(type); } - uint32_t GetModuleID() const { return module_id; } - uint32_t GetOffset() const { return offset; } - - operator lldb::addr_t() { return *(uint64_t *)this; } -}; - -static_assert(sizeof(wasm_addr_t) == 8, ""); - /// ProcessWasm provides the access to the Wasm program state /// retrieved from the Wasm engine. class ProcessWasm : public process_gdb_remote::ProcessGDBRemote { @@ -87,6 +59,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/source/Symbol/ObjectFile.cpp b/lldb/source/Symbol/ObjectFile.cpp index 51918e5f08424..89d01568de926 100644 --- a/lldb/source/Symbol/ObjectFile.cpp +++ b/lldb/source/Symbol/ObjectFile.cpp @@ -379,6 +379,7 @@ AddressClass ObjectFile::GetAddressClass(addr_t file_addr) { case eSectionTypeELFRelocationEntries: case eSectionTypeELFDynamicLinkInfo: case eSectionTypeWasmName: + case eSectionTypeWasmGlobal: case eSectionTypeOther: return AddressClass::eUnknown; case eSectionTypeAbsoluteAddress: diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py index a6c0dc9286b0c..861691f5b3ddc 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,60 @@ 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 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) + 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] + ) + + # 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") + self.assertTrue(global_section.IsValid()) + globals_addr = global_section.GetLoadAddress(target) + self.assertEqual(globals_addr, WASM_GLOBAL_ADDRESS | LOAD_ADDRESS) + + # A global is read as a whole value. + 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) + + # 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) + 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(globals_addr + 0, 8, error) + self.assertFalse(error.Success()) + self.assertIn("4-byte global", error.GetCString()) + + # Likewise for a global that does not exist. + process.ReadMemory(globals_addr + 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..5b1df63bea6a3 --- /dev/null +++ b/lldb/test/Shell/ObjectFile/wasm/wasm-globals.yaml @@ -0,0 +1,162 @@ +# 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 "image lookup -s g_answer" \ +# RUN: -o "target modules load --file %t.wasm --slide 0x0000000400000000" \ +# RUN: -o "target modules dump sections" \ +# RUN: -o "memory read -s 4 -c 1 -f x 0x8000000400000001" \ +# RUN: -o "memory read -s 8 -c 1 -f x 0x8000000400000002" \ +# RUN: -o "memory read -s 8 -c 1 -f x 0x8000000400000004" \ +# RUN: -o "memory read -s 4 -c 1 -f x 0x8000000400000005" \ +# RUN: -o "memory read -s 1 -c 1 -f x 0x8000000400000001" \ +# RUN: -o exit 2>&1 | FileCheck %s + +# The global section spans the whole index space, imported globals included, and +# has a range of its own so that an index is not also a code or data address. +# CHECK: Dumping sections +# CHECK: wasm-global {{.*}}[0x8000000000000000-0x8000000000000006) + +# Each named global becomes a data symbol spanning the one index it occupies. +# CHECK: Dumping symbol table +# CHECK: Data 0x8000000000000001 0x0000000000000001 {{.*}}g_answer +# CHECK: Data 0x8000000000000002 0x0000000000000001 {{.*}}g_double +# CHECK: Data 0x8000000000000004 0x0000000000000001 {{.*}}g_wide + +# A global resolves to its index within that space. +# CHECK: 1 symbols match 'g_answer' +# CHECK: (wasm-globals.yaml.tmp.wasm.global + 1) + +# Loading the module keeps the globals in their own space, with the module id. +# CHECK: Dumping sections +# CHECK: wasm-global {{.*}}[0x8000000400000000-0x8000000400000006) + +# With no process to ask, a global reads back as the value it is initialized +# with, in the byte order WebAssembly gives its memory. +# CHECK: 0x8000000400000001: 0x0000002a +# CHECK: 0x8000000400000002: 0x3ff8000000000000 +# CHECK: 0x8000000400000004: 0x00000000deadbeef + +# All ones is a value like any other, not the absence of one. +# CHECK: 0x8000000400000005: 0xffffffff + +# A type narrower than the global holding it reads the low bytes, which is how a +# char or short global is read. +# CHECK: 0x8000000400000001: 0x2a + +# 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. +# RUN: %lldb %t.wasm -o "image lookup -s g_imported" -o exit 2>&1 \ +# RUN: | FileCheck --check-prefix=NOSYMBOL %s +# NOSYMBOL-NOT: symbols match 'g_imported' + +# A read a global cannot serve fails rather than returning something plausible: +# an imported global has no initializer, an initializer that is not a constant +# has no value here, and reading past a global would have to come from somewhere +# else, because the next index is not adjacent storage. +# RUN: %lldb %t.wasm -o "memory read -s 4 -c 1 -f x 0x8000000000000000" -o exit 2>&1 \ +# RUN: | FileCheck --check-prefix=NOVALUE %s +# RUN: %lldb %t.wasm -o "memory read -s 4 -c 1 -f x 0x8000000000000003" -o exit 2>&1 \ +# RUN: | FileCheck --check-prefix=NOVALUE %s +# RUN: %lldb %t.wasm -o "memory read -s 8 -c 1 -f x 0x8000000000000001" -o exit 2>&1 \ +# RUN: | FileCheck --check-prefix=NOVALUE %s +# NOVALUE: error reading data from section global + +--- !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 + # A tag import is what a module built with C++ exceptions carries. Its + # descriptor has a shape of its own, which the import section has to know in + # order to find where the next import starts. + - Module: env + Field: __cpp_exception + Kind: TAG + SigIndex: 0 + - Type: FUNCTION + FunctionTypes: [ 0 ] + - Type: MEMORY + Memories: + - Minimum: 0x1 + - Type: GLOBAL + Globals: + - Index: 1 + Type: I32 + Mutable: false + InitExpr: + Opcode: I32_CONST + Value: 42 + # The bits of 1.5, a wider constant than the one before it, so the globals + # after this one only parse if this one was read at its own width. + - Index: 2 + Type: F64 + Mutable: false + InitExpr: + Opcode: F64_CONST + Value: 0x3FF8000000000000 + # Initialized from another global rather than from a constant, so its value + # is only known once the module has been instantiated. + - Index: 3 + Type: I32 + Mutable: false + InitExpr: + Opcode: GLOBAL_GET + Index: 0 + - Index: 4 + Type: I64 + Mutable: true + InitExpr: + Opcode: I64_CONST + Value: 3735928559 + - Index: 5 + Type: I32 + Mutable: false + InitExpr: + Opcode: I32_CONST + Value: -1 + - 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_double + - Index: 3 + Name: g_derived + - Index: 4 + Name: g_wide + - Index: 5 + Name: g_all_ones +... >From 6672a023b0dcdaef562a20cd4eaa4bb90b520286 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Wed, 29 Jul 2026 14:23:04 -0700 Subject: [PATCH 2/4] We like constants --- .../Plugins/ObjectFile/wasm/WasmAddress.h | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h index 2535f4580377a..8c731de38e36f 100644 --- a/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h +++ b/lldb/source/Plugins/ObjectFile/wasm/WasmAddress.h @@ -32,22 +32,42 @@ enum WasmAddressType : uint8_t { Invalid = 0x03, }; -/// The top two bits of a 64-bit address hold the space it belongs to. -static constexpr uint32_t kWasmAddressTypeShift = 62; -static constexpr uint64_t kWasmAddressTypeMask = uint64_t(0b11) - << kWasmAddressTypeShift; +/// Widths of the fields a 64-bit address is made of, from the low bits up. The +/// bitfields below are declared with the same constants, so a field and the +/// mask that extracts it cannot come to disagree. +static constexpr uint32_t kWasmOffsetBits = 32; +static constexpr uint32_t kWasmModuleIDBits = 30; +static constexpr uint32_t kWasmAddressTypeBits = 2; + +static_assert(kWasmOffsetBits + kWasmModuleIDBits + kWasmAddressTypeBits == 64, + "a Wasm address has to account for all 64 bits"); + +static constexpr uint32_t kWasmModuleIDShift = kWasmOffsetBits; +static constexpr uint32_t kWasmAddressTypeShift = + kWasmModuleIDShift + kWasmModuleIDBits; + +static constexpr uint64_t MakeFieldMask(uint32_t bits, uint32_t shift) { + return ((uint64_t(1) << bits) - 1) << shift; +} + +static constexpr uint64_t kWasmOffsetMask = MakeFieldMask(kWasmOffsetBits, 0); +static constexpr uint64_t kWasmModuleIDMask = + MakeFieldMask(kWasmModuleIDBits, kWasmModuleIDShift); +static constexpr uint64_t kWasmAddressTypeMask = + MakeFieldMask(kWasmAddressTypeBits, kWasmAddressTypeShift); /// For the purpose of debugging, we can represent all these separated 32-bit /// address spaces with a single virtual 64-bit address space. The /// wasm_addr_t provides this encoding using bitfields. struct wasm_addr_t { - uint64_t offset : 32; - uint64_t module_id : 30; - uint64_t type : 2; + uint64_t offset : kWasmOffsetBits; + uint64_t module_id : kWasmModuleIDBits; + uint64_t type : kWasmAddressTypeBits; wasm_addr_t(lldb::addr_t addr) - : offset(addr & 0x00000000ffffffff), - module_id((addr & 0x00ffffff00000000) >> 32), type(addr >> 62) {} + : offset(addr & kWasmOffsetMask), + module_id((addr & kWasmModuleIDMask) >> kWasmModuleIDShift), + type(addr >> kWasmAddressTypeShift) {} wasm_addr_t(WasmAddressType type, uint32_t module_id, uint32_t offset) : offset(offset), module_id(module_id), type(type) {} >From bf45d371a5decd45ef21442cc12568b6003f4c12 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Wed, 29 Jul 2026 14:28:17 -0700 Subject: [PATCH 3/4] Add FIXME --- lldb/source/Plugins/Process/wasm/ProcessWasm.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp index 4316d1aa1f6b2..7c5256aea9648 100644 --- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp +++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp @@ -89,6 +89,10 @@ std::shared_ptr<ThreadGDBRemote> ProcessWasm::CreateThread(lldb::tid_t tid) { size_t ProcessWasm::ReadGlobal(uint32_t index, void *buf, size_t size, Status &error) { // The protocol asks for a global relative to a frame, so a read needs one. + // FIXME: A global belongs to a module instance rather than to a frame, so the + // frame is only a proxy for the instance and the module the address names is + // ignored. A global in an instance with no active frame cannot be read at + // all. See https://github.com/llvm/llvm-project/issues/212833. ThreadSP thread = GetThreadList().GetSelectedThread(); StackFrameSP frame = thread ? thread->GetSelectedFrame(DoNoSelectMostRelevantFrame) : nullptr; >From 7b498601af7a9d1748dcbbe888c72cf88e35d81a Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Wed, 29 Jul 2026 14:50:28 -0700 Subject: [PATCH 4/4] Future proof the API --- .../Plugins/Process/wasm/ProcessWasm.cpp | 23 +++++++++++-------- .../source/Plugins/Process/wasm/ProcessWasm.h | 7 ++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp index 7c5256aea9648..e119b3e3ecf6d 100644 --- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp +++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp @@ -86,19 +86,21 @@ 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) { - // The protocol asks for a global relative to a frame, so a read needs one. - // FIXME: A global belongs to a module instance rather than to a frame, so the - // frame is only a proxy for the instance and the module the address names is - // ignored. A global in an instance with no active frame cannot be read at - // all. See https://github.com/llvm/llvm-project/issues/212833. +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::FromErrorString( - "Wasm global read failed: no frame to read the global from"); + error = Status::FromErrorStringWithFormatv( + "Wasm global read failed: no frame to read global {0} of module {1:x} " + "from", + index, module_id); return 0; } @@ -132,7 +134,8 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, case WasmAddressType::Object: return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error); case WasmAddressType::Global: - return ReadGlobal(wasm_addr.GetOffset(), buf, size, error); + return ReadGlobal(wasm_addr.GetModuleID(), wasm_addr.GetOffset(), buf, size, + error); case WasmAddressType::Invalid: break; } diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h index 44cbd6b16efcd..9bce07ec5691c 100644 --- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h +++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h @@ -59,8 +59,11 @@ 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); + /// 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); lldb::DynamicRegisterInfoSP &GetRegisterInfo() { return m_register_info_sp; } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
