| Issue |
208930
|
| Summary |
[ORC][MachO] Compact-unwind dso_base underflow when an object is emitted below the JITDylib header
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
cyx-6
|
### Summary
Under `MachOPlatform`, JIT-linking an object whose functions/LSDAs/personality GOT slot land at an address **below** the JITDylib header aborts linking with:
```
In <graph> __TEXT,__unwind_info, delta to end of functions 0x... exceeds 32 bits
```
(and similarly `... is out of 32-bit delta range of compact-unwind base at ...` for personalities).
### Root cause
`MachOPlatform` pins the `__jitlink$libunwind_dso_base` absolute symbol to the JITDylib header so that every object in the JITDylib shares a single compact-unwind base (to avoid synthesizing a header per object), in `MachOPlatform.cpp` (`modifyPassConfig`).
The `__unwind_info` writer encodes function/function-end/LSDA/personality offsets as **unsigned** 32-bit deltas from that base (`llvm/lib/ExecutionEngine/JITLink/CompactUnwindSupport.h`, `writePersonalities`/`writeIndexes`/`writeLSDAs`/`writeSecondLevelPages`, each guarded by `isUInt<32>`). `ExecutorAddrDiff` is a `uint64_t` and `operator-` wraps on underflow, so any covered address below the shared base becomes ~2^64 and fails the `isUInt<32>` check.
Nothing guarantees later graphs are laid out above the JITDylib header — the personality is first rewritten to a GOT entry in the *current* user graph, so the compared address belongs to this graph's allocation. A memory manager whose allocations can fall below the header (or simply an object linked before the header, or with a custom slab layout) triggers the abort. An upward-growing slab starting at the header only masks the problem by accident.
### Why widening to a signed delta is not the fix
The `__unwind_info` format stores unsigned image-base-relative u32 offsets that libunwind adds back to the registered `dso_base` at runtime (`libunwind/src/UnwindCursor.hpp`, `getInfoFromCompactEncodingSection`: `pc - sects.dso_base`, and `... + sects.dso_base`). Widening the JITLink check to `isInt<32>` would emit offsets the runtime cannot interpret. The base itself must be chosen so all deltas are non-negative.
### Reproduction (deterministic, link-time, host-agnostic)
`llvm-mc`/`llvm-jitlink` under `-noexec` reproduce the faulty math on any host (the compact-unwind writer runs at graph-build time). Define the base symbol above the code and place the slab below it:
```asm
.section __TEXT,__text,regular,pure_instructions
.globl _main
.p2align 2
_main:
.cfi_startproc
ret
.cfi_endproc
.globl __jitlink$libunwind_dso_base
.set __jitlink$libunwind_dso_base, 0xffff000000000000
.subsections_via_symbols
```
```
llvm-mc -triple=arm64-apple-darwin -filetype=obj -o neg.o neg.s
llvm-jitlink -noexec -num-threads=0 -entry=_main \
-slab-allocate 1Mb -slab-address 0x1000 -slab-page-size 4096 neg.o
# => ... __TEXT,__unwind_info, delta to end of functions 0x2004 exceeds 32 bits
```
Confirmed on real Apple Silicon with a released LLVM 22 toolchain, and cross-linking Mach-O on Linux.
### Proposed fix
Stop pinning the shared header as the compact-unwind base and let the writer synthesize a **per-graph** local Mach-O header (`getOrCreateCompactUnwindBase` -> `getOrCreateLocalMachOHeader`), which is forced to lay out first and so is always `<=` every covered address. Since libunwind decodes `offset + dso_base`, the per-graph base must also be reported to the runtime as `dso_base` (the runtime currently hard-codes the JITDylib header), which requires threading the base through the `register/deregister_object_platform_sections` wire.
PR forthcoming.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs