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

Reply via email to