[lldb] Create a BSS section for WebAssembly object files (#206840)

Zero-initialized globals (BSS) live in a module's linear memory above
the initialized data segments, but wasm-ld emits no data segment for
them, so ObjectFileWasm created no section covering their addresses.
Reading those global without a running process fails with "unable to
resolve the module for file address", because the address resolved to no
section. Live reads did work because they go through process memory.

Synthesize a zero-fill section spanning from the end of the initialized
data to the linear memory's minimum size, and map it into linear memory
like the data segments. Live reads keep going through process memory
while zero-fill reads return zero.
This commit is contained in:
Jonas Devlieghere 2026-06-30 16:09:58 -07:00 committed by GitHub
parent 1f7a93d088
commit 9a725a7e86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -486,6 +486,28 @@ static llvm::Expected<std::vector<WasmSegment>> ParseData(DataExtractor &data) {
return segments;
}
/// Parse the minimum size in bytes of the module's first linear memory. This is
/// the memory guaranteed to exist at instantiation, and so the upper bound of
/// the static data region.
static llvm::Expected<uint64_t> ParseMemoryMinSize(DataExtractor &data) {
lldb::offset_t offset = 0;
llvm::Expected<uint32_t> memory_count = GetULEB32(data, offset);
if (!memory_count)
return memory_count.takeError();
if (*memory_count == 0)
return llvm::createStringError("module declares no linear memory");
// The limits of a memory are a flags byte followed by the minimum, and
// optionally the maximum, page count.
data.GetU8(&offset);
llvm::Expected<uint32_t> min_pages = GetULEB32(data, offset);
if (!min_pages)
return min_pages.takeError();
return static_cast<uint64_t>(*min_pages) * llvm::wasm::WasmDefaultPageSize;
}
static llvm::Expected<std::vector<Symbol>>
ParseNames(SectionSP code_section_sp, DataExtractor &name_data,
const std::vector<WasmFunction> &functions,
@ -728,6 +750,7 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) {
}
lldb::user_id_t segment_id = 0;
lldb::addr_t static_data_end = 0;
for (const WasmSegment &segment : segments) {
if (segment.type == WasmSegment::Active) {
// FIXME: Support segments with a memory index.
@ -765,6 +788,36 @@ void ObjectFileWasm::CreateSections(SectionList &unified_section_list) {
/*log2align=*/0, /*flags=*/0);
m_sections_up->AddSection(segment_sp);
GetModule()->GetSectionList()->AddSection(segment_sp);
if (segment.type == WasmSegment::Active)
static_data_end = std::max(static_data_end, file_vm_addr + segment.size);
}
// Zero-initialized globals (BSS) have no data segment, so the loop above
// leaves their linear-memory addresses uncovered by any section, and a static
// read of one can't be resolved. Cover the rest of linear memory with a
// zero-fill section. SetLoadAddress maps it like a data segment so live reads
// still go through process memory.
if (std::optional<section_info> mem_info =
GetSectionInfo(llvm::wasm::WASM_SEC_MEMORY)) {
DataExtractor mem_data = ReadImageData(mem_info->offset, mem_info->size);
llvm::Expected<uint64_t> memory_size = ParseMemoryMinSize(mem_data);
if (!memory_size) {
LLDB_LOG_ERROR(log, memory_size.takeError(),
"Failed to parse Wasm memory section: {0}");
} else if (*memory_size > static_data_end) {
SectionSP bss_sp =
std::make_shared<Section>(GetModule(),
/*obj_file=*/this, ++segment_id << 8,
ConstString(".bss"), eSectionTypeZeroFill,
/*file_vm_addr=*/static_data_end,
/*vm_size=*/*memory_size - static_data_end,
/*file_offset=*/0,
/*file_size=*/0,
/*log2align=*/0, /*flags=*/0);
m_sections_up->AddSection(bss_sp);
GetModule()->GetSectionList()->AddSection(bss_sp);
}
}
}
@ -804,11 +857,11 @@ bool ObjectFileWasm::SetLoadAddress(Target &target, lldb::addr_t load_address,
for (size_t sect_idx = 0; sect_idx < num_sections; ++sect_idx) {
SectionSP section_sp(section_list->GetSectionAtIndex(sect_idx));
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
if (section_sp->GetType() == eSectionTypeData ||
section_sp->GetType() == eSectionTypeZeroFill) {
// Data and BSS sections 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();