https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/205913
Reading a global or static variable on a Wasm target produced a wrong value (or none at all). Two Wasm-only bugs combined to break it, both of which need to be fixed to support `target variable` / `frame var`. 1. DWARFExpression::Evaluate special-cased DW_OP_addr and DW_OP_addrx/DW_OP_GNU_addr_index on Wasm to push a LoadAddress, based on the theory that "Wasm file sections aren't mapped into memory". But a DW_OP_addr operand denotes a location in the module's address space, i.e. a file address like on every other target. Forcing a load address breaks the static (no-process) read path, since a file section cannot be read as a load address. 2. ObjectFileWasm::SetLoadAddress mapped every section with `load_address | GetFileOffset()`. For an active data segment that Object-tags the address (top bit = code space) and uses the file offset instead of the segment's linear-memory address, so a live read of a data global resolved to a garbage address in the wrong space. Address (1) by dropping the incorrect special casing. Address (2) by mapping data sections into the Memory space at their linear VM address while preserving the module id. Code and other sections keep their Object-space module-offset addressing. >From 978dadc0f6df559586d58cf5779c5ef7f96f8be3 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Thu, 25 Jun 2026 13:44:11 -0700 Subject: [PATCH] [lldb] Fix reading global variables on WebAssembly Reading a global or static variable on a Wasm target produced a wrong value (or none at all). Two Wasm-only bugs combined to break it, both of which need to be fixed to support `target variable` / `frame var`. 1. DWARFExpression::Evaluate special-cased DW_OP_addr and DW_OP_addrx/DW_OP_GNU_addr_index on Wasm to push a LoadAddress, based on the theory that "Wasm file sections aren't mapped into memory". But a DW_OP_addr operand denotes a location in the module's address space, i.e. a file address like on every other target. Forcing a load address breaks the static (no-process) read path, since a file section cannot be read as a load address. 2. ObjectFileWasm::SetLoadAddress mapped every section with `load_address | GetFileOffset()`. For an active data segment that Object-tags the address (top bit = code space) and uses the file offset instead of the segment's linear-memory address, so a live read of a data global resolved to a garbage address in the wrong space. Address (1) by dropping the incorrect special casing. Address (2) by mapping data sections into the Memory space at their linear VM address while preserving the module id. Code and other sections keep their Object-space module-offset addressing. --- lldb/source/Expression/DWARFExpression.cpp | 18 +---- .../ObjectFile/wasm/ObjectFileWasm.cpp | 18 ++++- .../wasm/wasm-data-section-load-address.yaml | 66 +++++++++++++++++++ .../Expression/DWARFExpressionTest.cpp | 9 +-- 4 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 lldb/test/Shell/ObjectFile/wasm/wasm-data-section-load-address.yaml diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp index 7966673bb65ed..66f33971fd220 100644 --- a/lldb/source/Expression/DWARFExpression.cpp +++ b/lldb/source/Expression/DWARFExpression.cpp @@ -1359,14 +1359,7 @@ llvm::Expected<Value> DWARFExpression::Evaluate( switch (opcode) { case DW_OP_addr: stack.push_back(Scalar(op->getRawOperand(0))); - if (eval_ctx.target && eval_ctx.target->GetArchitecture().GetCore() == - ArchSpec::eCore_wasm32) { - // wasm file sections aren't mapped into memory, therefore addresses can - // never point into a file section and are always LoadAddresses. - stack.back().SetValueType(Value::ValueType::LoadAddress); - } else { - stack.back().SetValueType(Value::ValueType::FileAddress); - } + stack.back().SetValueType(Value::ValueType::FileAddress); break; case DW_OP_deref: { @@ -1902,14 +1895,7 @@ llvm::Expected<Value> DWARFExpression::Evaluate( lldb::addr_t value = eval_ctx.dwarf_cu->ReadAddressFromDebugAddrSection(index); stack.push_back(Scalar(value)); - if (eval_ctx.target && eval_ctx.target->GetArchitecture().GetCore() == - ArchSpec::eCore_wasm32) { - // wasm file sections aren't mapped into memory, therefore addresses can - // never point into a file section and are always LoadAddresses. - stack.back().SetValueType(Value::ValueType::LoadAddress); - } else { - stack.back().SetValueType(Value::ValueType::FileAddress); - } + stack.back().SetValueType(Value::ValueType::FileAddress); } break; case DW_OP_GNU_const_index: { diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp index 205e549b66a22..9895044cd29bc 100644 --- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp @@ -803,10 +803,22 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address, const size_t num_sections = section_list->GetSize(); for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) { SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx)); - if (target.SetSectionLoadAddress( - section_sp, load_address | section_sp->GetFileOffset())) { - ++num_loaded_sections; + lldb::addr_t section_load_addr; + if (section_sp->GetType() == eSectionTypeData) { + // Active data segments are mapped into the module's linear memory at + // their virtual address. Linear memory is 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 segment at its linear VM + // address in the Memory space while preserving the module id. + section_load_addr = (load_address & ~(uint64_t(0b11) << 62)) | + section_sp->GetFileAddress(); + } else { + // Code (and other) sections are addressed by their offset within the + // module in the Object address space. + section_load_addr = load_address | section_sp->GetFileOffset(); } + if (target.SetSectionLoadAddress(section_sp, section_load_addr)) + ++num_loaded_sections; } return num_loaded_sections > 0; diff --git a/lldb/test/Shell/ObjectFile/wasm/wasm-data-section-load-address.yaml b/lldb/test/Shell/ObjectFile/wasm/wasm-data-section-load-address.yaml new file mode 100644 index 0000000000000..0681344de2cb7 --- /dev/null +++ b/lldb/test/Shell/ObjectFile/wasm/wasm-data-section-load-address.yaml @@ -0,0 +1,66 @@ +# REQUIRES: webassembly + +# An active data segment is mapped into the module's linear memory at the +# virtual address given by its init expression. Linear memory is a separate +# address space from code: a wasm 64-bit address encodes the space in its top +# two bits (0 = Memory, 1 = Object/code). Code is addressed in the Object space +# by its module offset; data must be addressed in the Memory space at its +# linear address. +# +# Here the data segment's init expr places it at linear address 0x10000. + +# RUN: yaml2obj %s -o %t.wasm +# RUN: %lldb %t.wasm \ +# RUN: -o "target modules dump sections" \ +# RUN: -o "target modules load --file %t.wasm --slide 0x4000000000000000" \ +# RUN: -o "target modules dump sections" \ +# RUN: -o exit 2>&1 | FileCheck %s + +# Before loading, sections carry their file addresses. The data segment's file +# address is its linear-memory address (from the init expr), not its offset in +# the wasm file. +# CHECK-LABEL: File Address +# CHECK: code {{.*}}[0x0000000000000000- +# CHECK: data {{.*}}[0x0000000000010000-0x0000000000010004) + +# After loading the module in the Object space (top bit set), the code section +# is placed in the Object space at its module offset, while the data segment is +# placed in the Memory space at its linear address. It is *not* Object-tagged. +# CHECK-LABEL: Load Address +# CHECK: code {{.*}}[0x40000000{{[0-9a-f]+}}- +# CHECK: data {{.*}}[0x0000000000010000-0x0000000000010004) + +--- !WASM +FileHeader: + Version: 0x1 +Sections: + - Type: TYPE + Signatures: + - Index: 0 + ParamTypes: [] + ReturnTypes: + - I32 + - Type: FUNCTION + FunctionTypes: [ 0 ] + - Type: MEMORY + Memories: + - Minimum: 0x2 + - Type: CODE + Functions: + - Index: 0 + Locals: [] + Body: 412A0F0B + - Type: DATA + Segments: + - SectionOffset: 6 + InitFlags: 0 + Offset: + Opcode: I32_CONST + Value: 65536 + Content: 'DEADBEEF' + - Type: CUSTOM + Name: name + FunctionNames: + - Index: 0 + Name: get_42 +... diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp index 7feacb2b9da24..130f2090a6865 100644 --- a/lldb/unittests/Expression/DWARFExpressionTest.cpp +++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp @@ -1179,10 +1179,11 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm")); ExecutionContext exe_ctx(test_ctx.target_sp, false); - // DW_OP_addr takes a single operand of address size width: + // DW_OP_addr takes a single operand of address size width. It denotes a + // location in the module's address space, i.e. a file address. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_addr, 0x40, 0x0, 0x0, 0x0}, {}, {}, &exe_ctx), - ExpectLoadAddress(0x40)); + ExpectFileAddress(0x40)); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { @@ -1255,11 +1256,11 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { DWARFExpression expr(extractor); llvm::Expected<Value> result = evaluate(expr); - EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x5678u)); + EXPECT_THAT_EXPECTED(result, ExpectFileAddress(0x5678u)); ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); result = evaluate(expr); - EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0xdeadbeefu)); + EXPECT_THAT_EXPECTED(result, ExpectFileAddress(0xdeadbeefu)); } class CustomSymbolFileDWARF : public SymbolFileDWARF { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
