From 6d8d8ebdbb1df200f587a54376f7b8e8ad65efa3 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 24 Sep 2026 06:37:24 +0800 Subject: [PATCH 1/3] wasm: preserve Memory64 DWARF address widths --- src/wasm/wasm-debug.cpp | 104 +++++++++++------- test/unit/input/dwarf-memory64.c | 12 ++ test/unit/input/dwarf/memory64.wasm | Bin 0 -> 989 bytes test/unit/test_dwarf.py | 26 +++++ third_party/llvm-project/DWARFEmitter.cpp | 13 ++- third_party/llvm-project/dwarf2yaml.cpp | 6 +- .../include/llvm/ObjectYAML/DWARFYAML.h | 4 +- 7 files changed, 117 insertions(+), 48 deletions(-) create mode 100644 test/unit/input/dwarf-memory64.c create mode 100755 test/unit/input/dwarf/memory64.wasm diff --git a/src/wasm/wasm-debug.cpp b/src/wasm/wasm-debug.cpp index c58e17b5fa1..3fd92e1a228 100644 --- a/src/wasm/wasm-debug.cpp +++ b/src/wasm/wasm-debug.cpp @@ -60,9 +60,9 @@ bool hasDWARFSections(const Module& wasm) { #ifdef BUILD_LLVM_DWARF -// In wasm32 the address size is 32 bits. -static const size_t AddressSize = 4; -static constexpr size_t RangeEntrySize = 2 * AddressSize; +static size_t getAddressSize(const Module& wasm) { + return !wasm.memories.empty() && wasm.memories[0]->is64() ? 8 : 4; +} // DWARF v6 reserves the all-ones address for a non-existent entity. LLVM also // recognizes max-minus-one in legacy range and location data, where all-ones @@ -73,11 +73,16 @@ static constexpr BinaryLocation LegacyTombstoneAddress = BinaryLocation(-2); static constexpr BinaryLocation EmptyRangeAddress = 1; static constexpr size_t NoParent = size_t(-1); +static uint64_t allOnesAddress(size_t addressSize) { + return addressSize == 8 ? uint64_t(-1) : uint32_t(-1); +} + struct BinaryenDWARFInfo { llvm::StringMap> sections; std::unique_ptr context; + size_t addressSize; - BinaryenDWARFInfo(const Module& wasm) { + BinaryenDWARFInfo(const Module& wasm) : addressSize(getAddressSize(wasm)) { // Get debug sections from the wasm. for (auto& section : wasm.customSections) { if (Name(section.name).startsWith(".debug_") && section.data.data()) { @@ -87,7 +92,7 @@ struct BinaryenDWARFInfo { } } // Parse debug sections. - uint8_t addrSize = AddressSize; + uint8_t addrSize = addressSize; bool isLittleEndian = true; context = llvm::DWARFContext::create(sections, addrSize, isLittleEndian); if (context->getMaxVersion() > 4) { @@ -295,15 +300,15 @@ struct LineState { void emitDiff(const LineState& old, std::vector& newOpcodes, const llvm::DWARFYAML::LineTable& table, + size_t addressSize, bool endSequence) const { bool useSpecial = false; if (addr != old.addr || line != old.line) { // Try to use a special opcode TODO } if (addr != old.addr && !useSpecial) { - // len = 1 (subopcode) + 4 (wasm32 address) - // FIXME: look at AddrSize on the Unit. - auto item = makeItem(llvm::dwarf::DW_LNE_set_address, 5); + // The address operand has the compilation unit's address width. + auto item = makeItem(llvm::dwarf::DW_LNE_set_address, 1 + addressSize); item.Data = addr; newOpcodes.push_back(item); } @@ -692,7 +697,8 @@ static bool isTombstone(uint32_t x) { return x == 0 || isNonzeroTombstone(x); } // Update debug lines, and update the locationUpdater with debug line offset // changes so we can update offsets into the debug line section. static void updateDebugLines(llvm::DWARFYAML::Data& data, - LocationUpdater& locationUpdater) { + LocationUpdater& locationUpdater, + size_t addressSize) { for (auto& table : data.DebugLines) { uint32_t sequenceId = 0; // Parse the original opcodes and emit new ones. @@ -784,7 +790,7 @@ static void updateDebugLines(llvm::DWARFYAML::Data& data, bool endSequence = i + 1 == newAddrs.size() || newAddrInfo.at(newAddrs[i + 1]).sequenceId != state.sequenceId; - state.emitDiff(lastState, newOpcodes, table, endSequence); + state.emitDiff(lastState, newOpcodes, table, addressSize, endSequence); } table.Opcodes.swap(newOpcodes); } @@ -800,7 +806,9 @@ static void updateDebugLines(llvm::DWARFYAML::Data& data, auto oldLocation = table.Position; locationUpdater.debugLineMap[oldLocation] = newLocation; table.Position = newLocation; - newLocation += computedLengths[i] + AddressSize; + // The initial-length field is four bytes in DWARF32 and twelve in + // DWARF64. It is independent of the target address width. + newLocation += computedLengths[i] + (table.Length.isDWARF64() ? 12 : 4); table.Length.setLength(computedLengths[i]); } } @@ -975,26 +983,31 @@ static void updateCompileUnits(const BinaryenDWARFInfo& info, } static void updateRanges(llvm::DWARFYAML::Data& yaml, - const LocationUpdater& locationUpdater) { + const LocationUpdater& locationUpdater, + size_t addressSize) { // In each range section, update the start and end. If either endpoint no // longer has a mapping, emit an empty range that a debugger can safely // ignore. Do not use (0, 0), since that is the list terminator. for (auto& range : yaml.Ranges) { - BinaryLocation oldStart = range.Start, oldEnd = range.End, newStart = 0, - newEnd = 0; - if ((oldStart == 0 && oldEnd == 0) || oldStart == AllOnesAddress) { + uint64_t oldStart = range.Start, oldEnd = range.End, newStart = 0, + newEnd = 0; + if ((oldStart == 0 && oldEnd == 0) || + oldStart == allOnesAddress(addressSize)) { newStart = oldStart; newEnd = oldEnd; - } else if (oldStart == LegacyTombstoneAddress || isTombstone(oldEnd)) { + } else if (BinaryLocation(oldStart) == LegacyTombstoneAddress || + isTombstone(BinaryLocation(oldEnd))) { newStart = EmptyRangeAddress; newEnd = EmptyRangeAddress; } else { // Zero is a valid offset from the current range-list base. It is only a // tombstone when paired with a zero end as handled above. - newStart = oldStart == 0 ? 0 : locationUpdater.getNewStart(oldStart); - newEnd = locationUpdater.getNewEnd(oldEnd); - if ((oldStart != 0 && isTombstone(newStart)) || isTombstone(newEnd) || - newEnd <= newStart) { + newStart = oldStart == 0 + ? 0 + : locationUpdater.getNewStart(BinaryLocation(oldStart)); + newEnd = locationUpdater.getNewEnd(BinaryLocation(oldEnd)); + if ((oldStart != 0 && isTombstone(BinaryLocation(newStart))) || + isTombstone(BinaryLocation(newEnd)) || newEnd <= newStart) { newStart = EmptyRangeAddress; newEnd = EmptyRangeAddress; } @@ -1028,7 +1041,8 @@ struct DIEAddressInfo { static void readDIEAddressRanges(DIEAddressInfo& info, llvm::DWARFYAML::Data& yaml, - BinaryLocation compileUnitBase) { + BinaryLocation compileUnitBase, + size_t rangeEntrySize) { std::optional lowPC; std::optional highPC; std::optional rangesOffset; @@ -1052,14 +1066,14 @@ static void readDIEAddressRanges(DIEAddressInfo& info, if (rangesOffset) { info.hasRangeDescription = true; - if (*rangesOffset % RangeEntrySize != 0 || - *rangesOffset / RangeEntrySize >= yaml.Ranges.size()) { + if (*rangesOffset % rangeEntrySize != 0 || + *rangesOffset / rangeEntrySize >= yaml.Ranges.size()) { info.malformed = true; return; } auto base = uint64_t(compileUnitBase); bool terminated = false; - for (size_t i = *rangesOffset / RangeEntrySize; i < yaml.Ranges.size(); + for (size_t i = *rangesOffset / rangeEntrySize; i < yaml.Ranges.size(); ++i) { auto start = BinaryLocation(yaml.Ranges[i].Start); auto end = BinaryLocation(yaml.Ranges[i].End); @@ -1106,12 +1120,16 @@ static void readDIEAddressRanges(DIEAddressInfo& info, info.rangeListDirty |= info.rangesValue && info.ranges.normalize(); } -static void writeRangeList(DIEAddressInfo& info, llvm::DWARFYAML::Data& yaml) { +static void writeRangeList(DIEAddressInfo& info, + llvm::DWARFYAML::Data& yaml, + size_t rangeEntrySize, + size_t addressSize) { assert(info.rangesValue); - info.rangesValue->Value = yaml.Ranges.size() * RangeEntrySize; + info.rangesValue->Value = yaml.Ranges.size() * rangeEntrySize; // Use an explicit zero base so the new entries remain absolute and can be // updated again without recovering an implicit compile-unit base. - yaml.Ranges.push_back(llvm::DWARFYAML::Range{AllOnesAddress, 0, 0}); + yaml.Ranges.push_back( + llvm::DWARFYAML::Range{allOnesAddress(addressSize), 0, 0}); for (auto [start, end] : info.ranges.get()) { yaml.Ranges.push_back(llvm::DWARFYAML::Range{start, end, 0}); } @@ -1137,20 +1155,23 @@ static void markUnavailable(std::vector& infos, static void writeUnavailableDIE(DIEAddressInfo& info, llvm::DWARFYAML::Data& yaml, - std::optional& emptyRangeListOffset) { + std::optional& emptyRangeListOffset, + size_t rangeEntrySize, + size_t addressSize) { iterContextAndYAML( info.abbrevDecl->attributes(), info.yamlEntry->Values, [&](const llvm::DWARFAbbreviationDeclaration::AttributeSpec& attrSpec, llvm::DWARFYAML::FormValue& yamlValue) { if (attrSpec.Attr == llvm::dwarf::DW_AT_low_pc) { - yamlValue.Value = AllOnesAddress; + yamlValue.Value = allOnesAddress(addressSize); } else if (attrSpec.Attr == llvm::dwarf::DW_AT_high_pc) { - yamlValue.Value = - attrSpec.Form == llvm::dwarf::DW_FORM_data4 ? 0 : AllOnesAddress; + yamlValue.Value = attrSpec.Form == llvm::dwarf::DW_FORM_data4 + ? 0 + : allOnesAddress(addressSize); } else if (attrSpec.Attr == llvm::dwarf::DW_AT_ranges) { if (!emptyRangeListOffset) { - emptyRangeListOffset = yaml.Ranges.size() * RangeEntrySize; + emptyRangeListOffset = yaml.Ranges.size() * rangeEntrySize; yaml.Ranges.push_back(llvm::DWARFYAML::Range{0, 0, 0}); } yamlValue.Value = *emptyRangeListOffset; @@ -1161,6 +1182,7 @@ writeUnavailableDIE(DIEAddressInfo& info, static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, llvm::DWARFYAML::Data& yaml, const LocationUpdater& locationUpdater) { + const size_t rangeEntrySize = 2 * dwarfInfo.addressSize; size_t compileUnitIndex = 0; std::optional emptyRangeListOffset; iterContextAndYAML( @@ -1199,7 +1221,7 @@ static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, ancestors[info.depth] = index; ancestors.resize(info.depth + 1); } - readDIEAddressRanges(info, yaml, compileUnitBase); + readDIEAddressRanges(info, yaml, compileUnitBase, rangeEntrySize); } ++yamlEntry; ++index; @@ -1282,9 +1304,13 @@ static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, for (auto& info : infos) { if (info.abbrevDecl && info.unavailable) { - writeUnavailableDIE(info, yaml, emptyRangeListOffset); + writeUnavailableDIE(info, + yaml, + emptyRangeListOffset, + rangeEntrySize, + dwarfInfo.addressSize); } else if (info.abbrevDecl && info.rangeListDirty) { - writeRangeList(info, yaml); + writeRangeList(info, yaml, rangeEntrySize, dwarfInfo.addressSize); } } ++compileUnitIndex; @@ -1296,7 +1322,7 @@ static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, static const BinaryLocation IGNOREABLE_LOCATION = 1; static bool isNewBaseLoc(const llvm::DWARFYAML::Loc& loc) { - return loc.Start == BinaryLocation(-1); + return loc.Start == uint64_t(-1) || loc.Start == uint32_t(-1); } static bool isEndMarkerLoc(const llvm::DWARFYAML::Loc& loc) { @@ -1325,7 +1351,7 @@ static void updateLoc(llvm::DWARFYAML::Data& yaml, atStart = false; } // By default we copy values over, unless we modify them below. - BinaryLocation newStart = loc.Start, newEnd = loc.End; + uint64_t newStart = loc.Start, newEnd = loc.End; if (isNewBaseLoc(loc)) { // This is a new base. // Note that the base is not the address of an instruction, necessarily - @@ -1410,12 +1436,12 @@ void writeDWARFSections(Module& wasm, const BinaryLocations& newLocations) { LocationUpdater locationUpdater(wasm, newLocations); - updateDebugLines(data, locationUpdater); + updateDebugLines(data, locationUpdater, info.addressSize); bool is64 = wasm.memories.size() > 0 ? wasm.memories[0]->is64() : false; updateCompileUnits(info, data, locationUpdater, is64); - updateRanges(data, locationUpdater); + updateRanges(data, locationUpdater, info.addressSize); repairDIEAddressRanges(info, data, locationUpdater); diff --git a/test/unit/input/dwarf-memory64.c b/test/unit/input/dwarf-memory64.c new file mode 100644 index 00000000000..c20d6de7466 --- /dev/null +++ b/test/unit/input/dwarf-memory64.c @@ -0,0 +1,12 @@ +__attribute__((always_inline)) static inline long add_seven(long input) { + long local = input + 7; + return local; +} + +__attribute__((noinline)) long debug_probe(long input) { + long before = add_seven(input); + if (input > 0) { + before += add_seven(input + 1); + } + return before; +} diff --git a/test/unit/input/dwarf/memory64.wasm b/test/unit/input/dwarf/memory64.wasm new file mode 100755 index 0000000000000000000000000000000000000000..f813b3201ca74ef620deb3beb55c7b3a875d6d3e GIT binary patch literal 989 zcmZWnOK;Oa5T5lrF-d4cA5m2l2`UhllctKQ3dBQ!3kM`F2sog!wl|iAy|(OiN>J3l z!HwRz@DunmT>A?+z^t8;0Bd<(JM-=Aw*$0D2mla+yeH@6TvZ6@D$$6!s~8DO(tNIf z9Wbv9PqRGr7(jD}Y(XEtiM5zW1+VrN6LVq1s5z)J;9}{l)K}74 z)=To=z| znjzCn?GkDdYu~~cJe1p}+-}@Mv5CK2@*rYvG^>*1Dg>~&dZ9w(5zk5ieoT4TpRv4% zQr3@hYea7Oc~ zThGw(c;EvhSMrW47a@yPE?}0vP;9v)(j}$1DWTT`ZBkI%92(ICd&x(Qtt~V!o z#Z_a)(HGJYG!L0LoiZxQoD~hn_R9Ff^43mg&2iIAINL~I>_$9@a^?#+McyTyj&;?d z(@e@(L^B$f%;_YhpkB<}Fit%h7fzR_T;w#%A|ATaOr#MPPA6wmmNV`%SIje3I93sb I++`odZw!~wW&i*H literal 0 HcmV?d00001 diff --git a/test/unit/test_dwarf.py b/test/unit/test_dwarf.py index b750bf4e1b5..d4d2b6f00e8 100644 --- a/test/unit/test_dwarf.py +++ b/test/unit/test_dwarf.py @@ -1,4 +1,5 @@ import os +import shutil import subprocess import tempfile @@ -8,6 +9,31 @@ class DWARFTest(utils.BinaryenTestCase): + def test_memory64_address_width(self): + # Regenerate the checked-in input from dwarf-memory64.c with: + # clang -target wasm64-unknown-unknown -O1 -g -gdwarf-4 \ + # -fdebug-compilation-dir=/binaryen -c dwarf-memory64.c -o input.o + # wasm-ld -mwasm64 --no-entry --export=debug_probe input.o -o memory64.wasm + source = self.input_path(os.path.join('dwarf', 'memory64.wasm')) + dwarfdump = shutil.which('llvm-dwarfdump') + with tempfile.TemporaryDirectory() as temp_dir: + for name, args in [('roundtrip', ['--roundtrip']), + ('asyncify', ['--asyncify'])]: + output = os.path.join(temp_dir, name + '.wasm') + shared.run_process(shared.WASM_OPT + + [source, '-g', *args, '-o', output]) + dump = shared.run_process(shared.WASM_OPT + + [output, '--dwarfdump'], + capture_output=True).stdout + self.assertIn('debug_probe', dump) + if dwarfdump: + verify = subprocess.run([dwarfdump, '--verify', output], + capture_output=True, text=True) + diagnostics = verify.stdout + verify.stderr + self.assertEqual(verify.returncode, 0, diagnostics) + self.assertNotIn('mismatching address size', diagnostics) + self.assertIn('No errors.', diagnostics) + def test_tombstone_roundtrip(self): def custom_section(name, contents): name = name.encode() diff --git a/third_party/llvm-project/DWARFEmitter.cpp b/third_party/llvm-project/DWARFEmitter.cpp index 7c66a82fae7..3629532c9f1 100644 --- a/third_party/llvm-project/DWARFEmitter.cpp +++ b/third_party/llvm-project/DWARFEmitter.cpp @@ -124,9 +124,10 @@ void DWARFYAML::EmitDebugRanges(raw_ostream &OS, const DWARFYAML::Data &DI) { // format is totally trivial, consisting just of pairs of address // sized addresses describing the ranges." and apparently it ends // with a null termination of a pair of zeros + const auto AddrSize = DI.CompileUnits.empty() ? 4 : DI.CompileUnits[0].AddrSize; for (auto Range : DI.Ranges) { - writeInteger((uint32_t)Range.Start, OS, DI.IsLittleEndian); - writeInteger((uint32_t)Range.End, OS, DI.IsLittleEndian); + writeVariableSizedInteger(Range.Start, AddrSize, OS, DI.IsLittleEndian); + writeVariableSizedInteger(Range.End, AddrSize, OS, DI.IsLittleEndian); } } @@ -134,14 +135,14 @@ void DWARFYAML::EmitDebugRanges(raw_ostream &OS, const DWARFYAML::Data &DI) { void DWARFYAML::EmitDebugLoc(raw_ostream &OS, const DWARFYAML::Data &DI) { for (auto Loc : DI.Locs) { auto AddrSize = DI.CompileUnits[0].AddrSize; // XXX BINARYEN - // FIXME: Loc.Start etc should probably not be 32-bit. - writeVariableSizedInteger((uint64_t)(int32_t)Loc.Start, AddrSize, OS, DI.IsLittleEndian); - writeVariableSizedInteger((uint64_t)(int32_t)Loc.End, AddrSize, OS, DI.IsLittleEndian); + writeVariableSizedInteger(Loc.Start, AddrSize, OS, DI.IsLittleEndian); + writeVariableSizedInteger(Loc.End, AddrSize, OS, DI.IsLittleEndian); if (Loc.Start == 0 && Loc.End == 0) { // End of a list. continue; } - if (Loc.Start != -1) { + const uint64_t baseMarker = AddrSize == 8 ? uint64_t(-1) : uint32_t(-1); + if (Loc.Start != baseMarker) { writeInteger((uint16_t)Loc.Location.size(), OS, DI.IsLittleEndian); for (auto x : Loc.Location) { writeInteger((uint8_t)x, OS, DI.IsLittleEndian); diff --git a/third_party/llvm-project/dwarf2yaml.cpp b/third_party/llvm-project/dwarf2yaml.cpp index 13f5b968c0f..7acae000796 100644 --- a/third_party/llvm-project/dwarf2yaml.cpp +++ b/third_party/llvm-project/dwarf2yaml.cpp @@ -90,7 +90,11 @@ void dumpDebugARanges(DWARFContext &DCtx, DWARFYAML::Data &Y) { } void dumpDebugRanges(DWARFContext &DCtx, DWARFYAML::Data &Y) { // XXX BINARYEN - uint8_t savedAddressByteSize = 4; + auto CUS = DCtx.normal_units(); + if (CUS.empty()) { + return; + } + uint8_t savedAddressByteSize = CUS.begin()->get()->getAddressByteSize(); DWARFDataExtractor rangesData(DCtx.getDWARFObj(), DCtx.getDWARFObj().getRangesSection(), DCtx.isLittleEndian(), savedAddressByteSize); uint64_t offset = 0; diff --git a/third_party/llvm-project/include/llvm/ObjectYAML/DWARFYAML.h b/third_party/llvm-project/include/llvm/ObjectYAML/DWARFYAML.h index bb66565a5e0..74064b60809 100644 --- a/third_party/llvm-project/include/llvm/ObjectYAML/DWARFYAML.h +++ b/third_party/llvm-project/include/llvm/ObjectYAML/DWARFYAML.h @@ -89,8 +89,8 @@ struct Range { }; struct Loc { - uint32_t Start; - uint32_t End; + uint64_t Start; + uint64_t End; std::vector Location; uint64_t CompileUnitOffset; }; From 6be7fe4c9ed28bf86bd6ebf824a1f2f02d08907b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 24 Sep 2026 06:37:44 +0800 Subject: [PATCH 2/3] test: store Memory64 fixture as data --- test/unit/input/dwarf/memory64.wasm | Bin 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 test/unit/input/dwarf/memory64.wasm diff --git a/test/unit/input/dwarf/memory64.wasm b/test/unit/input/dwarf/memory64.wasm old mode 100755 new mode 100644 From b5f893db4337c842b48e31a9d45e0d464f93febc Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 24 Sep 2026 09:08:31 +0800 Subject: [PATCH 3/3] Fix memory64 DWARF review findings --- src/wasm/wasm-debug.cpp | 115 ++++++++++++----- test/unit/test_dwarf.py | 147 ++++++++++++++++++++++ third_party/llvm-project/DWARFEmitter.cpp | 16 ++- third_party/llvm-project/dwarf2yaml.cpp | 6 +- 4 files changed, 243 insertions(+), 41 deletions(-) diff --git a/src/wasm/wasm-debug.cpp b/src/wasm/wasm-debug.cpp index 3fd92e1a228..37b2cdc332c 100644 --- a/src/wasm/wasm-debug.cpp +++ b/src/wasm/wasm-debug.cpp @@ -336,8 +336,14 @@ struct LineState { newOpcodes.push_back(item); } if (discriminator != old.discriminator) { - // len = 1 (subopcode) + 4 (wasm32 address) - auto item = makeItem(llvm::dwarf::DW_LNE_set_discriminator, 5); + // The discriminator operand is ULEB128, independent of address width. + uint32_t value = discriminator; + size_t operandSize = 1; + while (value >>= 7) { + ++operandSize; + } + auto item = + makeItem(llvm::dwarf::DW_LNE_set_discriminator, 1 + operandSize); item.Data = discriminator; newOpcodes.push_back(item); } @@ -694,6 +700,28 @@ static bool isNonzeroTombstone(BinaryLocation location) { // a real address zero must use isNonzeroTombstone instead. static bool isTombstone(uint32_t x) { return x == 0 || isNonzeroTombstone(x); } +// DWARF addresses may be 64 bits, but mapped code locations are byte offsets +// into a Wasm binary, which BinaryLocation currently represents in 32 bits. +// An out-of-range DWARF value has no mapping; never truncate it into one. +static BinaryLocation getMappedStart(const LocationUpdater& locationUpdater, + uint64_t oldAddress) { + return oldAddress > UINT32_MAX + ? 0 + : locationUpdater.getNewStart(BinaryLocation(oldAddress)); +} + +static BinaryLocation getMappedEnd(const LocationUpdater& locationUpdater, + uint64_t oldAddress) { + return oldAddress > UINT32_MAX + ? 0 + : locationUpdater.getNewEnd(BinaryLocation(oldAddress)); +} + +static bool isEncodedTombstone(uint64_t address, size_t addressSize) { + return address == 0 || address == allOnesAddress(addressSize) || + address == allOnesAddress(addressSize) - 1; +} + // Update debug lines, and update the locationUpdater with debug line offset // changes so we can update offsets into the debug line section. static void updateDebugLines(llvm::DWARFYAML::Data& data, @@ -809,7 +837,12 @@ static void updateDebugLines(llvm::DWARFYAML::Data& data, // The initial-length field is four bytes in DWARF32 and twelve in // DWARF64. It is independent of the target address width. newLocation += computedLengths[i] + (table.Length.isDWARF64() ? 12 : 4); + bool isDWARF64 = table.Length.isDWARF64(); table.Length.setLength(computedLengths[i]); + if (isDWARF64) { + table.Length.TotalLength = UINT32_MAX; + table.Length.TotalLength64 = computedLengths[i]; + } } } @@ -995,17 +1028,15 @@ static void updateRanges(llvm::DWARFYAML::Data& yaml, oldStart == allOnesAddress(addressSize)) { newStart = oldStart; newEnd = oldEnd; - } else if (BinaryLocation(oldStart) == LegacyTombstoneAddress || - isTombstone(BinaryLocation(oldEnd))) { + } else if (oldStart == allOnesAddress(addressSize) - 1 || + isEncodedTombstone(oldEnd, addressSize)) { newStart = EmptyRangeAddress; newEnd = EmptyRangeAddress; } else { // Zero is a valid offset from the current range-list base. It is only a // tombstone when paired with a zero end as handled above. - newStart = oldStart == 0 - ? 0 - : locationUpdater.getNewStart(BinaryLocation(oldStart)); - newEnd = locationUpdater.getNewEnd(BinaryLocation(oldEnd)); + newStart = oldStart == 0 ? 0 : getMappedStart(locationUpdater, oldStart); + newEnd = getMappedEnd(locationUpdater, oldEnd); if ((oldStart != 0 && isTombstone(BinaryLocation(newStart))) || isTombstone(BinaryLocation(newEnd)) || newEnd <= newStart) { newStart = EmptyRangeAddress; @@ -1042,10 +1073,11 @@ struct DIEAddressInfo { static void readDIEAddressRanges(DIEAddressInfo& info, llvm::DWARFYAML::Data& yaml, BinaryLocation compileUnitBase, - size_t rangeEntrySize) { - std::optional lowPC; - std::optional highPC; - std::optional rangesOffset; + size_t rangeEntrySize, + size_t addressSize) { + std::optional lowPC; + std::optional highPC; + std::optional rangesOffset; bool highPCIsRelative = false; iterContextAndYAML( @@ -1054,12 +1086,12 @@ static void readDIEAddressRanges(DIEAddressInfo& info, [&](const llvm::DWARFAbbreviationDeclaration::AttributeSpec& attrSpec, llvm::DWARFYAML::FormValue& yamlValue) { if (attrSpec.Attr == llvm::dwarf::DW_AT_low_pc) { - lowPC = BinaryLocation(yamlValue.Value); + lowPC = yamlValue.Value; } else if (attrSpec.Attr == llvm::dwarf::DW_AT_high_pc) { - highPC = BinaryLocation(yamlValue.Value); + highPC = yamlValue.Value; highPCIsRelative = attrSpec.Form == llvm::dwarf::DW_FORM_data4; } else if (attrSpec.Attr == llvm::dwarf::DW_AT_ranges) { - rangesOffset = BinaryLocation(yamlValue.Value); + rangesOffset = yamlValue.Value; info.rangesValue = &yamlValue; } }); @@ -1075,19 +1107,20 @@ static void readDIEAddressRanges(DIEAddressInfo& info, bool terminated = false; for (size_t i = *rangesOffset / rangeEntrySize; i < yaml.Ranges.size(); ++i) { - auto start = BinaryLocation(yaml.Ranges[i].Start); - auto end = BinaryLocation(yaml.Ranges[i].End); + auto start = yaml.Ranges[i].Start; + auto end = yaml.Ranges[i].End; if (start == 0 && end == 0) { terminated = true; break; } - if (start == AllOnesAddress) { + if (start == allOnesAddress(addressSize)) { base = end; continue; } // A zero start is a valid offset from the current base. Only (0, 0), // handled above, terminates the list. - if (start == LegacyTombstoneAddress || isTombstone(end)) { + if (start == allOnesAddress(addressSize) - 1 || + isEncodedTombstone(end, addressSize)) { continue; } auto absoluteStart = base + start; @@ -1106,7 +1139,8 @@ static void readDIEAddressRanges(DIEAddressInfo& info, // Unlike a range-list terminator, zero is a valid low_pc when paired with // a nonzero high_pc. Only the reserved nonzero sentinels are unambiguously // unavailable in this encoding. - if (!isNonzeroTombstone(*lowPC)) { + if (*lowPC != allOnesAddress(addressSize) && + *lowPC != allOnesAddress(addressSize) - 1) { uint64_t start = *lowPC; uint64_t end = highPCIsRelative ? start + *highPC : *highPC; if (start > end) { @@ -1221,7 +1255,8 @@ static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, ancestors[info.depth] = index; ancestors.resize(info.depth + 1); } - readDIEAddressRanges(info, yaml, compileUnitBase, rangeEntrySize); + readDIEAddressRanges( + info, yaml, compileUnitBase, rangeEntrySize, dwarfInfo.addressSize); } ++yamlEntry; ++index; @@ -1321,17 +1356,18 @@ static void repairDIEAddressRanges(const BinaryenDWARFInfo& dwarfInfo, // would indicate an end or a base in .debug_loc). static const BinaryLocation IGNOREABLE_LOCATION = 1; -static bool isNewBaseLoc(const llvm::DWARFYAML::Loc& loc) { - return loc.Start == uint64_t(-1) || loc.Start == uint32_t(-1); +static bool isNewBaseLoc(const llvm::DWARFYAML::Loc& loc, size_t addressSize) { + return loc.Start == allOnesAddress(addressSize); } static bool isEndMarkerLoc(const llvm::DWARFYAML::Loc& loc) { - return isTombstone(loc.Start) && isTombstone(loc.End); + return loc.Start == 0 && loc.End == 0; } // Update the .debug_loc section. static void updateLoc(llvm::DWARFYAML::Data& yaml, - const LocationUpdater& locationUpdater) { + const LocationUpdater& locationUpdater, + size_t addressSize) { // Similar to ranges, try to update the start and end. Note that here we // can't skip since the location description is a variable number of bytes, // so we mark no longer valid addresses as empty. @@ -1341,7 +1377,18 @@ static void updateLoc(llvm::DWARFYAML::Data& yaml, // base entries around (a base entry is added to every entry after it in the // list). However, we may change the base's value as after moving instructions // around the old base may not be smaller than all the values relative to it. - BinaryLocation oldBase, newBase; + uint64_t oldBase; + BinaryLocation newBase; + auto mapStart = [&](uint64_t offset) { + return offset > UINT32_MAX || oldBase > UINT32_MAX - offset + ? BinaryLocation(0) + : getMappedStart(locationUpdater, oldBase + offset); + }; + auto mapEnd = [&](uint64_t offset) { + return offset > UINT32_MAX || oldBase > UINT32_MAX - offset + ? BinaryLocation(0) + : getMappedEnd(locationUpdater, oldBase + offset); + }; auto& locs = yaml.Locs; for (size_t i = 0; i < locs.size(); i++) { auto& loc = locs[i]; @@ -1352,7 +1399,7 @@ static void updateLoc(llvm::DWARFYAML::Data& yaml, } // By default we copy values over, unless we modify them below. uint64_t newStart = loc.Start, newEnd = loc.End; - if (isNewBaseLoc(loc)) { + if (isNewBaseLoc(loc, addressSize)) { // This is a new base. // Note that the base is not the address of an instruction, necessarily - // it's just a number (seems like it could always be an instruction, but @@ -1361,15 +1408,15 @@ static void updateLoc(llvm::DWARFYAML::Data& yaml, // can emit a new proper base (as mentioned earlier, the original base may // not be valid if instructions moved to a position before it - they must // be positive offsets from it). - oldBase = newBase = newEnd; + oldBase = newEnd; + newBase = 0; BinaryLocation smallest = -1; for (size_t j = i + 1; j < locs.size(); j++) { auto& futureLoc = locs[j]; - if (isNewBaseLoc(futureLoc) || isEndMarkerLoc(futureLoc)) { + if (isNewBaseLoc(futureLoc, addressSize) || isEndMarkerLoc(futureLoc)) { break; } - auto updatedStart = - locationUpdater.getNewStart(futureLoc.Start + oldBase); + auto updatedStart = mapStart(futureLoc.Start); // If we found a valid mapping, this is a relevant value for us. If the // optimizer removed it, it's a 0, and we can ignore it here - we will // emit IGNOREABLE_LOCATION for it later anyhow. @@ -1391,8 +1438,8 @@ static void updateLoc(llvm::DWARFYAML::Data& yaml, // This is a normal entry, try to find what it should be updated to. First // de-relativize it to the base to get the absolute address, then look for // a new address for it. - newStart = locationUpdater.getNewStart(loc.Start + oldBase); - newEnd = locationUpdater.getNewEnd(loc.End + oldBase); + newStart = mapStart(loc.Start); + newEnd = mapEnd(loc.End); if (newStart == 0 || newEnd == 0 || newStart > newEnd) { // This part of the loc no longer has a mapping, or after the mapping // it is no longer a proper span, so we must ignore it. @@ -1445,7 +1492,7 @@ void writeDWARFSections(Module& wasm, const BinaryLocations& newLocations) { repairDIEAddressRanges(info, data, locationUpdater); - updateLoc(data, locationUpdater); + updateLoc(data, locationUpdater, info.addressSize); // Convert to binary sections. auto newSections = diff --git a/test/unit/test_dwarf.py b/test/unit/test_dwarf.py index d4d2b6f00e8..1e5ad78b06f 100644 --- a/test/unit/test_dwarf.py +++ b/test/unit/test_dwarf.py @@ -8,6 +8,66 @@ from . import utils +def read_uleb(data, offset): + value = 0 + shift = 0 + while True: + byte = data[offset] + offset += 1 + value |= (byte & 0x7f) << shift + if byte < 0x80: + return value, offset + shift += 7 + + +def write_uleb(value): + data = bytearray() + while value >= 0x80: + data.append((value & 0x7f) | 0x80) + value >>= 7 + data.append(value) + return bytes(data) + + +def custom_section(data, name): + offset = 8 + while offset < len(data): + kind = data[offset] + size, payload = read_uleb(data, offset + 1) + end = payload + size + if kind == 0: + name_size, contents = read_uleb(data, payload) + section_name = data[contents:contents + name_size].decode() + if section_name == name: + return data[contents + name_size:end] + offset = end + raise ValueError(name) + + +def replace_custom_section(data, name, replacement): + offset = 8 + while offset < len(data): + kind = data[offset] + size, payload = read_uleb(data, offset + 1) + end = payload + size + if kind == 0: + name_size, contents = read_uleb(data, payload) + section_name = data[contents:contents + name_size].decode() + if section_name == name: + name_bytes = data[contents:contents + name_size] + new_payload = write_uleb(name_size) + name_bytes + replacement + return (data[:offset] + b'\0' + + write_uleb(len(new_payload)) + new_payload + data[end:]) + offset = end + raise ValueError(name) + + +def append_custom_section(data, name, contents): + name_bytes = name.encode() + payload = write_uleb(len(name_bytes)) + name_bytes + contents + return data + b'\0' + write_uleb(len(payload)) + payload + + class DWARFTest(utils.BinaryenTestCase): def test_memory64_address_width(self): # Regenerate the checked-in input from dwarf-memory64.c with: @@ -34,6 +94,93 @@ def test_memory64_address_width(self): self.assertNotIn('mismatching address size', diagnostics) self.assertIn('No errors.', diagnostics) + def test_memory64_dwarf64_line_discriminator(self): + source = self.input_path(os.path.join('dwarf', 'memory64.wasm')) + with open(source, 'rb') as f: + wasm = f.read() + line = custom_section(wasm, '.debug_line') + prologue_length = int.from_bytes(line[6:10], 'little') + opcodes = 10 + prologue_length + set_address = line.index(b'\x00\x09\x02', opcodes) + first_row = set_address + 11 # opcode, length, subopcode, 8-byte address + discriminator = b'\x00\x03\x04\xac\x02' # DW_LNE_set_discriminator 300 + line = line[:first_row] + discriminator + line[first_row:] + # Convert the line table to DWARF64, independently of the CU's + # address size and the DWARF32 format used by .debug_info. + body_length = len(line) # four more prologue bytes replace four prefix bytes + line = (b'\xff' * 4 + body_length.to_bytes(8, 'little') + + line[4:6] + prologue_length.to_bytes(8, 'little') + line[10:]) + wasm = replace_custom_section(wasm, '.debug_line', line) + + with tempfile.TemporaryDirectory() as temp_dir: + input_file = os.path.join(temp_dir, 'input.wasm') + output_file = os.path.join(temp_dir, 'output.wasm') + with open(input_file, 'wb') as f: + f.write(wasm) + shared.run_process(shared.WASM_OPT + + [input_file, '--roundtrip', '-g', '-o', output_file]) + with open(output_file, 'rb') as f: + output = f.read() + output_line = custom_section(output, '.debug_line') + self.assertEqual(output_line[:4], b'\xff' * 4) + self.assertEqual(int.from_bytes(output_line[4:12], 'little'), + len(output_line) - 12) + self.assertIn(discriminator, output_line) + dwarfdump = shutil.which('llvm-dwarfdump') + if dwarfdump: + verify = subprocess.run([dwarfdump, '--verify', output_file], + capture_output=True, text=True) + self.assertEqual(verify.returncode, 0, + verify.stdout + verify.stderr) + lines = subprocess.run([dwarfdump, '--debug-line', output_file], + capture_output=True, text=True) + self.assertIn('DWARF64', lines.stdout) + self.assertIn('300', lines.stdout) + + def test_memory64_loc_value_with_32_bit_all_ones(self): + source = self.input_path(os.path.join('dwarf', 'memory64.wasm')) + with open(source, 'rb') as f: + wasm = f.read() + loc = custom_section(wasm, '.debug_loc') + # A 64-bit value with only its low 32 bits set is a normal location, + # not a base-address-selection marker or list terminator. + loc = ((0xffffffff).to_bytes(8, 'little') + + (0x100000000).to_bytes(8, 'little') + loc[16:]) + wasm = replace_custom_section(wasm, '.debug_loc', loc) + + with tempfile.TemporaryDirectory() as temp_dir: + input_file = os.path.join(temp_dir, 'input.wasm') + output_file = os.path.join(temp_dir, 'output.wasm') + with open(input_file, 'wb') as f: + f.write(wasm) + shared.run_process(shared.WASM_OPT + + [input_file, '--roundtrip', '-g', '-o', output_file]) + with open(output_file, 'rb') as f: + output_loc = custom_section(f.read(), '.debug_loc') + self.assertEqual(len(output_loc), len(loc)) + self.assertEqual(output_loc[:16], + (1).to_bytes(8, 'little') * 2) + + def test_memory64_range_outside_binary_offset(self): + source = self.input_path(os.path.join('dwarf', 'memory64.wasm')) + with open(source, 'rb') as f: + wasm = f.read() + ranges = ((0x100000003).to_bytes(8, 'little') + + (0x100000004).to_bytes(8, 'little') + b'\0' * 16) + wasm = append_custom_section(wasm, '.debug_ranges', ranges) + + with tempfile.TemporaryDirectory() as temp_dir: + input_file = os.path.join(temp_dir, 'input.wasm') + output_file = os.path.join(temp_dir, 'output.wasm') + with open(input_file, 'wb') as f: + f.write(wasm) + shared.run_process(shared.WASM_OPT + + [input_file, '--roundtrip', '-g', '-o', output_file]) + with open(output_file, 'rb') as f: + output_ranges = custom_section(f.read(), '.debug_ranges') + self.assertEqual(output_ranges[:16], + (1).to_bytes(8, 'little') * 2) + def test_tombstone_roundtrip(self): def custom_section(name, contents): name = name.encode() diff --git a/third_party/llvm-project/DWARFEmitter.cpp b/third_party/llvm-project/DWARFEmitter.cpp index 3629532c9f1..d907dfe44f8 100644 --- a/third_party/llvm-project/DWARFEmitter.cpp +++ b/third_party/llvm-project/DWARFEmitter.cpp @@ -133,8 +133,8 @@ void DWARFYAML::EmitDebugRanges(raw_ostream &OS, const DWARFYAML::Data &DI) { // XXX BINARYEN void DWARFYAML::EmitDebugLoc(raw_ostream &OS, const DWARFYAML::Data &DI) { + const auto AddrSize = DI.CompileUnits.empty() ? 4 : DI.CompileUnits[0].AddrSize; for (auto Loc : DI.Locs) { - auto AddrSize = DI.CompileUnits[0].AddrSize; // XXX BINARYEN writeVariableSizedInteger(Loc.Start, AddrSize, OS, DI.IsLittleEndian); writeVariableSizedInteger(Loc.End, AddrSize, OS, DI.IsLittleEndian); if (Loc.Start == 0 && Loc.End == 0) { @@ -307,10 +307,12 @@ static void EmitDebugLineInternal(raw_ostream &RealOS, writeInteger((uint8_t)Op.SubOpcode, OS, DI.IsLittleEndian); switch (Op.SubOpcode) { case dwarf::DW_LNE_set_address: - case dwarf::DW_LNE_set_discriminator: writeVariableSizedInteger(Op.Data, DI.CompileUnits[0].AddrSize, OS, DI.IsLittleEndian); break; + case dwarf::DW_LNE_set_discriminator: + encodeULEB128(Op.Data, OS); + break; case dwarf::DW_LNE_define_file: EmitFileEntry(OS, Op.FileEntry); break; @@ -353,15 +355,19 @@ static void EmitDebugLineInternal(raw_ostream &RealOS, } } // XXX BINARYEN Write to the actual stream, with the proper size. - // We assume for now that the length fits in 32 bits. size_t Size = OS.str().size(); - if (Size >= UINT32_MAX) { + if (!LineTable.Length.isDWARF64() && Size >= UINT32_MAX) { llvm_unreachable("Table is too big"); } if (computedLengths) { computedLengths->push_back(Size); } - writeInteger((uint32_t)Size, RealOS, DI.IsLittleEndian); + if (LineTable.Length.isDWARF64()) { + writeInteger(UINT32_MAX, RealOS, DI.IsLittleEndian); + writeInteger((uint64_t)Size, RealOS, DI.IsLittleEndian); + } else { + writeInteger((uint32_t)Size, RealOS, DI.IsLittleEndian); + } RealOS << OS.str(); } } diff --git a/third_party/llvm-project/dwarf2yaml.cpp b/third_party/llvm-project/dwarf2yaml.cpp index 7acae000796..6106378d2e3 100644 --- a/third_party/llvm-project/dwarf2yaml.cpp +++ b/third_party/llvm-project/dwarf2yaml.cpp @@ -372,7 +372,7 @@ void dumpDebugLines(DWARFContext &DCtx, DWARFYAML::Data &Y) { } const uint64_t LineEnd = - LineTableLength + *StmtOffset + SizeOfPrologueLength; + LineTableLength + *StmtOffset + (DebugLines.Length.isDWARF64() ? 12 : 4); while (Offset < LineEnd) { DWARFYAML::LineTableOpcode NewOp = {}; NewOp.Opcode = (dwarf::LineNumberOps)LineData.getU8(&Offset); @@ -383,9 +383,11 @@ void dumpDebugLines(DWARFContext &DCtx, DWARFYAML::Data &Y) { (dwarf::LineNumberExtendedOps)LineData.getU8(&Offset); switch (NewOp.SubOpcode) { case dwarf::DW_LNE_set_address: - case dwarf::DW_LNE_set_discriminator: NewOp.Data = LineData.getAddress(&Offset); break; + case dwarf::DW_LNE_set_discriminator: + NewOp.Data = LineData.getULEB128(&Offset); + break; case dwarf::DW_LNE_define_file: dumpFileEntry(LineData, Offset, NewOp.FileEntry); break;