Issue 208440
Summary [BOLT] partitionCUs emits a spurious "tries to read DIEs" DWARF warning with --update-debug-sections
Labels BOLT
Assignees
Reporter not4juu
    ## Description

A regression from #197859 makes `llvm-bolt --update-debug-sections` print one bogus DWARF warning per compilation unit whose abbreviation table uses `DW_FORM_ref_addr`. The rewritten output is still correct; the warning is caused by an off-by-one in the new manual DIE-tree walk in `partitionCUs()`.

## Summary

This is a BOLT regression introduced by #197859 (commit `d3ac9b5c4cab`, *[RFC][BOLT] Add a new parallel DWARF processing (2/2)*), in `bolt/lib/Rewrite/DWARFRewriter.cpp` → `partitionCUs()`. It is a deterministic logic bug, not a data race: it reproduces with the default parallel run and equally single-threaded (`-debug-thread-count=1`), because `partitionCUs()` walks each CU sequentially regardless of thread count. The problem is architecture-independent, since the faulty code walks DWARF rather than target instructions.

## Problem observation

The warning is produced purely by DWARF rewriting, so no optimization options are needed to trigger it. The minimal reproduction is:

```bash
llvm-bolt input.so -o /dev/null --update-debug-sections
```

It prints, once for every CU that has at least one abbreviation using `DW_FORM_ref_addr`:

```text
DWARF unit from offset 0x........ incl. to offset 0x........ excl. tries to read DIEs at offset 0x........
```

(the `incl.`/`excl.` offsets are the unit bounds, and the last offset equals the next unit's start offset).

`partitionCUs()` only walks the DIE tree of CUs whose abbreviation table references another unit via `DW_FORM_ref_addr`:

```cpp
if (RefAddrAbbrevs.empty())
  continue;   // CUs without cross-CU ref_addr are skipped entirely
```

so the warning appears once per such CU.

## Root cause

`partitionCUs()` walks each qualifying CU's DIE tree with a manual DFS. The `ParentIndex` stack is seeded with a `UINT32_MAX` **sentinel** to mark the root:

```cpp
SmallVector<uint32_t, 8> ParentIndex;
ParentIndex.push_back(UINT32_MAX);          // root sentinel
do {
  if (!DIEEntry.extractFast(*CU, &DIEOffset, DebugInfoData,
                            NextCUOffset, ParentIndex.back()))
    break;
  // ...
  if (Abbrev) {
    // ...
    if (Abbrev->hasChildren())
      ParentIndex.push_back(0);
  } else {
    ParentIndex.pop_back();                 // pops a null-DIE level
  }
} while (!ParentIndex.empty());             // <-- sentinel is never popped
```

The sentinel is **never popped** (there is exactly one null DIE per level with children, and none for the root sentinel itself). After the last top-level DIE's null terminator is consumed:

- `DIEOffset` already equals `NextCUOffset`, **but**
- `ParentIndex` is not empty (it still holds `UINT32_MAX`),

so the loop runs **one extra iteration** and calls `extractFast()` with `DIEOffset == NextCUOffset`. That out-of-bounds read is what emits the warning in `DWARFDebugInfoEntry::extractFast`:

```cpp
// llvm/lib/DebugInfo/DWARF/DWARFDebugInfoEntry.cpp
if (Offset >= UEndOffset) {
  U.getContext().getWarningHandler()(createStringError(
      errc::invalid_argument,
      "DWARF unit from offset 0x%8.8" PRIx64 " incl. to offset 0x%8.8" PRIx64
      " excl. tries to read DIEs at offset 0x%8.8" PRIx64,
      U.getOffset(), U.getNextUnitOffset(), *OffsetPtr));
  return false;
}
```

The cross-reference result is unaffected — the extra `extractFast()` returns `false` and the loop `break`s after all real DIEs have already been processed — so the only visible effect is the spurious warning.

## Proposed fix

Terminate the loop once the CU has been fully traversed, before another read is attempted:

```diff
diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp
--- a/bolt/lib/Rewrite/DWARFRewriter.cpp
+++ b/bolt/lib/Rewrite/DWARFRewriter.cpp
@@ static SmallVector<SmallVector<DWARFUnit *>> partitionCUs(DWARFContext &DwCtx,
       } else {
         ParentIndex.pop_back();
       }
-    } while (!ParentIndex.empty());
+    } while (DIEOffset < NextCUOffset && !ParentIndex.empty());
   }
```

Because `DIEOffset` reaches `NextCUOffset` only after the whole unit has been consumed, this cannot drop a legitimate DIE. `extractFast()` already bounds-checks the offset, so this change does not add safety — it simply avoids the benign out-of-bounds call that produces the warning.

> Note that with the sentinel design `ParentIndex` never actually becomes empty, so `!ParentIndex.empty()` is effectively dead. An equally correct and slightly cleaner alternative is to make the offset bound the sole condition:

```cpp
} while (DIEOffset < NextCUOffset);
```

Maintainers may also prefer **not to seed the sentinel at all**, or to loop while `ParentIndex.size() > 1`; all of these are semantically equivalent.


## Environment

- **llvm-bolt:** built from `main` (reproduced on `4405d6e56d43` and `f05387745e53`)
- **Verified on:** aarch64 (architecture-independent can be reproduced at x86_64)
- **Compiler**: g++ (GCC) 14.2.0

_Follow-up fix for #197859 — cc @Thrrreeee_

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

Reply via email to