[GH-ISSUE #5752] [Docs] IL2CPP native-linking guide omits ambiguous universal-slice refusal #3498

Closed
opened 2026-10-31 00:41:10 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5752

Before submitting

  • I searched the existing issues for a related documentation report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Documentation location

docs/il2cpp-metadata.md, Native method addresses, especially lines 189–193 and the native-linking reason table at lines 271–285.

Problem

The guide says a universal Mach-O needs binary_architecture (arm64 or amd64) and lists slice-selection-required and slice-not-found. It omits the intentional ambiguous-slice-selection refusal when multiple slices share one accepted architecture label. Supplying the documented selector can therefore still leave selection unresolved, without the guide explaining why or what thinning can and cannot recover.

An independent verifier reproduced this with a copy of the stock macOS binary whose exact slices are x86_64, arm64e, and arm64e.x1. Both ARM-family slices map to REA's arm64 selector. This report concerns the missing guidance, not a request to choose an arbitrary slice or add a selector.

Expected content

Document ambiguous-slice-selection as an unsupported native-linking outcome and explain that binary_architecture must identify exactly one universal slice.

Explain the recovery boundaries: enumerate exact subtype names with lipo -info, extract the intended exact subtype into a separate thin file (for example, lipo <universal> -thin arm64e -output <thin>), and omit REA's architecture option when inspecting that thin file. lipo -thin arm64 does not select arm64e on the verified sample because there is no exact arm64 slice. Thinning bypasses ambiguity only; matching metadata/binary requirements and unsupported pointer encodings still apply.

Keep the current intentional ambiguity refusal. The existing boundary test explicitly requires refusing ambiguity instead of choosing the first FAT slice.

Supporting evidence

Independently executed from the built source checkout on rea-agents 6.7.0, commit 9d8e7803326a382cbbb3ffdb900f5e93e5442d68, Node.js v26.12.0, macOS 27.0.1 (26A434), arm64. The stock binary copy was inspected as data and never executed. First confirm the listed architectures; a binary with only one ARM slice does not trigger this case.

work_dir="$(mktemp -d)"
mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data"   "$work_dir/cache" "$work_dir/state" "$work_dir/tmp"
python3 -I - "$work_dir/global-metadata.dat" <<'PYTHON'
import struct, sys
# Version 29 has 31 (offset, byte-size) pairs after magic and version.
# Every empty section starts at the end of the 256-byte header.
header = struct.pack('<Ii', 0xfab11baf, 29)
header += b''.join(struct.pack('<ii', 256, 0) for _ in range(31))
with open(sys.argv[1], 'wb') as f:
    f.write(header)
PYTHON
cp -c /bin/ls "$work_dir/universal"
lipo -info "$work_dir/universal"
# Observed: x86_64 arm64e arm64e.x1
run_rea() {
  env -i PATH="$PATH" HOME="$work_dir/home"     XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data"     XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state"     TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent     node scripts/rea.mjs inspect-il2cpp-metadata     "$work_dir/global-metadata.dat" "$@" --json
}
run_rea > "$work_dir/metadata-only.json"
echo "metadata only: $?"
run_rea --binary "$work_dir/universal" > "$work_dir/no-architecture.json"
echo "no architecture: $?"
run_rea --binary "$work_dir/universal" --architecture arm64   > "$work_dir/arm64.json"
echo "arm64: $?"
run_rea --binary "$work_dir/universal" --architecture amd64   > "$work_dir/amd64.json"
echo "amd64: $?"
lipo "$work_dir/universal" -thin arm64e -output "$work_dir/thin-arm64e"
run_rea --binary "$work_dir/thin-arm64e" > "$work_dir/thin.json"
echo "thin arm64e, no architecture: $?"

Observed outcomes:

  • Metadata only: exit 0.
  • Universal binary without architecture: exit 1, unsupported_target, slice-selection-required: universal Mach-O holds amd64, arm64, arm64; select one architecture.
  • Universal binary with --architecture arm64: exit 1, unsupported_target, ambiguous-slice-selection: multiple universal slices declare arm64; no slice was selected.
  • Universal binary with --architecture amd64: passes slice selection, then exits 1 with invalid_request; the binary-path issue is invalid_format, code-registration-not-found: multiple CodeRegistration module tables match.
  • Exact arm64e extraction succeeds. Inspecting the thin file without architecture passes slice selection, then exits 1 with the documented unsupported-pointer-encoding: Mach-O chained fixups are not decoded.

Source-derived explanation: src/il2cpp/Il2cppBinaryImage.ts:36–56 refuses multiple slices matching the coarse architecture; src/il2cpp/Il2cppBinaryFormat.ts:2–9 classifies this as unsupported. tests/boundary/il2cpp/il2cppBinaryFile.test.ts:464–469 pins that refusal. The test was inspected, not run for this report. Searching docs/ finds no occurrence of ambiguous-slice-selection.

Submission-time main recheck: fetched origin/main at d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f. No changes since the verified baseline in the cited guide, contract guidance, implementation, metadata fixture parser, or boundary test; no new runtime reproduction on main was needed or claimed.

Additional context

P3 documentation omission: no unintended native-linking regression, wrong addresses, or data loss was demonstrated. The stock binary is not an IL2CPP binary matching the synthetic metadata. The amd64 and thin controls demonstrate passing slice selection only, not successful native linking. Broader Unity-build frequency was not measured.

Related but distinct: #1765 concerns inspect-artifact subtype naming and integrity admission; #5191 and #5254 concern native-inspector slice names and format reporting. This report concerns the separate IL2CPP reader's intentionally refused ambiguity and the missing explanation in its guide. #2040 changes native-inspector/dispatch selection, not this IL2CPP contract. Prepared with AI assistance and independently verified before submission.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5752 ### Before submitting - [x] I searched the existing issues for a related documentation report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Documentation location [`docs/il2cpp-metadata.md`, Native method addresses](https://github.com/morluto/rea/blob/9d8e7803326a382cbbb3ffdb900f5e93e5442d68/docs/il2cpp-metadata.md#native-method-addresses), especially lines 189–193 and the native-linking reason table at lines 271–285. ### Problem The guide says a universal Mach-O needs `binary_architecture` (`arm64` or `amd64`) and lists `slice-selection-required` and `slice-not-found`. It omits the intentional `ambiguous-slice-selection` refusal when multiple slices share one accepted architecture label. Supplying the documented selector can therefore still leave selection unresolved, without the guide explaining why or what thinning can and cannot recover. An independent verifier reproduced this with a copy of the stock macOS binary whose exact slices are `x86_64`, `arm64e`, and `arm64e.x1`. Both ARM-family slices map to REA's `arm64` selector. This report concerns the missing guidance, not a request to choose an arbitrary slice or add a selector. ### Expected content Document `ambiguous-slice-selection` as an **unsupported** native-linking outcome and explain that `binary_architecture` must identify exactly one universal slice. Explain the recovery boundaries: enumerate exact subtype names with `lipo -info`, extract the intended exact subtype into a separate thin file (for example, `lipo <universal> -thin arm64e -output <thin>`), and omit REA's architecture option when inspecting that thin file. `lipo -thin arm64` does not select `arm64e` on the verified sample because there is no exact `arm64` slice. Thinning bypasses ambiguity only; matching metadata/binary requirements and unsupported pointer encodings still apply. Keep the current intentional ambiguity refusal. The existing boundary test explicitly requires refusing ambiguity instead of choosing the first FAT slice. ### Supporting evidence Independently executed from the built source checkout on `rea-agents` 6.7.0, commit `9d8e7803326a382cbbb3ffdb900f5e93e5442d68`, Node.js v26.12.0, macOS 27.0.1 (26A434), arm64. The stock binary copy was inspected as data and never executed. First confirm the listed architectures; a binary with only one ARM slice does not trigger this case. ```sh work_dir="$(mktemp -d)" mkdir -p "$work_dir/home" "$work_dir/cfg" "$work_dir/data" "$work_dir/cache" "$work_dir/state" "$work_dir/tmp" python3 -I - "$work_dir/global-metadata.dat" <<'PYTHON' import struct, sys # Version 29 has 31 (offset, byte-size) pairs after magic and version. # Every empty section starts at the end of the 256-byte header. header = struct.pack('<Ii', 0xfab11baf, 29) header += b''.join(struct.pack('<ii', 256, 0) for _ in range(31)) with open(sys.argv[1], 'wb') as f: f.write(header) PYTHON cp -c /bin/ls "$work_dir/universal" lipo -info "$work_dir/universal" # Observed: x86_64 arm64e arm64e.x1 run_rea() { env -i PATH="$PATH" HOME="$work_dir/home" XDG_CONFIG_HOME="$work_dir/cfg" XDG_DATA_HOME="$work_dir/data" XDG_CACHE_HOME="$work_dir/cache" XDG_STATE_HOME="$work_dir/state" TMPDIR="$work_dir/tmp" REA_LOG_LEVEL=silent node scripts/rea.mjs inspect-il2cpp-metadata "$work_dir/global-metadata.dat" "$@" --json } run_rea > "$work_dir/metadata-only.json" echo "metadata only: $?" run_rea --binary "$work_dir/universal" > "$work_dir/no-architecture.json" echo "no architecture: $?" run_rea --binary "$work_dir/universal" --architecture arm64 > "$work_dir/arm64.json" echo "arm64: $?" run_rea --binary "$work_dir/universal" --architecture amd64 > "$work_dir/amd64.json" echo "amd64: $?" lipo "$work_dir/universal" -thin arm64e -output "$work_dir/thin-arm64e" run_rea --binary "$work_dir/thin-arm64e" > "$work_dir/thin.json" echo "thin arm64e, no architecture: $?" ``` Observed outcomes: - Metadata only: exit 0. - Universal binary without architecture: exit 1, `unsupported_target`, `slice-selection-required: universal Mach-O holds amd64, arm64, arm64; select one architecture`. - Universal binary with `--architecture arm64`: exit 1, `unsupported_target`, `ambiguous-slice-selection: multiple universal slices declare arm64; no slice was selected`. - Universal binary with `--architecture amd64`: passes slice selection, then exits 1 with `invalid_request`; the binary-path issue is `invalid_format`, `code-registration-not-found: multiple CodeRegistration module tables match`. - Exact `arm64e` extraction succeeds. Inspecting the thin file without architecture passes slice selection, then exits 1 with the documented `unsupported-pointer-encoding: Mach-O chained fixups are not decoded`. Source-derived explanation: `src/il2cpp/Il2cppBinaryImage.ts:36–56` refuses multiple slices matching the coarse architecture; `src/il2cpp/Il2cppBinaryFormat.ts:2–9` classifies this as unsupported. `tests/boundary/il2cpp/il2cppBinaryFile.test.ts:464–469` pins that refusal. The test was inspected, not run for this report. Searching `docs/` finds no occurrence of `ambiguous-slice-selection`. Submission-time main recheck: fetched `origin/main` at `d0067ba34a39ea47dd7e11bb2e6169bc0938cc3f`. No changes since the verified baseline in the cited guide, contract guidance, implementation, metadata fixture parser, or boundary test; no new runtime reproduction on main was needed or claimed. ### Additional context P3 documentation omission: no unintended native-linking regression, wrong addresses, or data loss was demonstrated. The stock binary is not an IL2CPP binary matching the synthetic metadata. The amd64 and thin controls demonstrate passing slice selection only, not successful native linking. Broader Unity-build frequency was not measured. Related but distinct: #1765 concerns `inspect-artifact` subtype naming and integrity admission; #5191 and #5254 concern native-inspector slice names and format reporting. This report concerns the separate IL2CPP reader's intentionally refused ambiguity and the missing explanation in its guide. #2040 changes native-inspector/dispatch selection, not this IL2CPP contract. Prepared with AI assistance and independently verified before submission.
Author
Owner

@XinwangTao commented on GitHub (Oct 21, 2026):

Good catch — the doc currently implies binary_architecture alone resolves slice selection, so a real-world FAT binary with both arm64e and arm64e.x1 under the arm64 label leaves the user stuck with no documented path. The proposed recovery boundaries are exactly right: enumerate exact subtype names via lipo -info, thin to the exact subtype, and omit the architecture option for the thin file. One small suggestion for the wording: make it explicit that the refusal is per-architecture-label, not per-slice — i.e. "binary_architecture must identify exactly one universal slice" could be sharpened to "must identify exactly one slice under that architecture label", otherwise a user with one arm64e and one arm64e.x1 slice may still think passing arm64 should be sufficient. Also worth a one-line note that lipo -thin arm64e.x1 (with the dot suffix) is the escaping hatch for the verified sample, since the guide's example only shows the bare name.

<!-- gh-comment-id:6311341267 --> @XinwangTao commented on GitHub (Oct 21, 2026): Good catch — the doc currently implies binary_architecture alone resolves slice selection, so a real-world FAT binary with both arm64e and arm64e.x1 under the arm64 label leaves the user stuck with no documented path. The proposed recovery boundaries are exactly right: enumerate exact subtype names via lipo -info, thin to the exact subtype, and omit the architecture option for the thin file. One small suggestion for the wording: make it explicit that the refusal is per-architecture-label, not per-slice — i.e. "binary_architecture must identify exactly one universal slice" could be sharpened to "must identify exactly one slice under that architecture label", otherwise a user with one arm64e and one arm64e.x1 slice may still think passing arm64 should be sufficient. Also worth a one-line note that lipo -thin arm64e.x1 (with the dot suffix) is the escaping hatch for the verified sample, since the guide's example only shows the bare name.
Author
Owner

@Iyanuoluwa007 commented on GitHub (Oct 21, 2026):

Fix is ready: docs/il2cpp-ambiguous-slice (commit 610cb498e). It documents ambiguous-slice-selection as an unsupported native-linking outcome, both in "Native method addresses" and in the reason table, and explains the lipo -info / lipo -thin <exact subtype> recovery, including that thinning resolves only the ambiguity. The refusal itself is unchanged. I'll open the PR as soon as one of my open PR slots frees up (the 3-open-PR limit).

<!-- gh-comment-id:6315574169 --> @Iyanuoluwa007 commented on GitHub (Oct 21, 2026): Fix is ready: [`docs/il2cpp-ambiguous-slice`](https://github.com/Iyanuoluwa007/rea/tree/docs/il2cpp-ambiguous-slice) (commit 610cb498e). It documents `ambiguous-slice-selection` as an unsupported native-linking outcome, both in "Native method addresses" and in the reason table, and explains the `lipo -info` / `lipo -thin <exact subtype>` recovery, including that thinning resolves only the ambiguity. The refusal itself is unchanged. I'll open the PR as soon as one of my open PR slots frees up (the 3-open-PR limit).
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

确认修复方向覆盖了我先前提的两点:lipo -info / lipo -thin 恢复路径和"thinning 只解决歧义、拒绝本身不变"的边界说明,610cb49 的加入是正确的一步。两点建议供开 PR 前核对:一是 commit 里的 reason table 条目是否已明确"按架构标签拒绝、而非按 slice 拒绝"(一个 arm64e + 一个 arm64e.x1 的 FAT 都标 arm64 时,用户需要知道传 arm64 仍不够);二是术语与 #5191/#5254 涉及的 native-inspector slice 名称保持同一套命名,避免 docs 里 ambiguous-slice-selection 与 inspector 的 slice 术语口径不一致。另外 #2040 正在改 native-inspector/dispatch 选择,建议 PR 合并前确认与这条文档不冲突。

<!-- gh-comment-id:6330534943 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认修复方向覆盖了我先前提的两点:lipo -info / lipo -thin <exact subtype> 恢复路径和"thinning 只解决歧义、拒绝本身不变"的边界说明,610cb49 的加入是正确的一步。两点建议供开 PR 前核对:一是 commit 里的 reason table 条目是否已明确"按架构标签拒绝、而非按 slice 拒绝"(一个 arm64e + 一个 arm64e.x1 的 FAT 都标 arm64 时,用户需要知道传 arm64 仍不够);二是术语与 #5191/#5254 涉及的 native-inspector slice 名称保持同一套命名,避免 docs 里 ambiguous-slice-selection 与 inspector 的 slice 术语口径不一致。另外 #2040 正在改 native-inspector/dispatch 选择,建议 PR 合并前确认与这条文档不冲突。
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

The guide now explicitly says multiple slices sharing one architecture label are refused, names ambiguous-slice-selection, documents lipo -info and exact-subtype thinning followed by inspection without architecture, and states thinning resolves only ambiguity. The reason table contains the refusal. Existing parser regression asserts refusal instead of first-slice choice.

Implementation: il2cpp-metadata.md, il2cpp-metadata.md.

The relevant regression checks in il2cppBinaryFile.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380758315 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The guide now explicitly says multiple slices sharing one architecture label are refused, names ambiguous-slice-selection, documents lipo -info and exact-subtype thinning followed by inspection without architecture, and states thinning resolves only ambiguity. The reason table contains the refusal. Existing parser regression asserts refusal instead of first-slice choice. Implementation: [il2cpp-metadata.md](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/docs/il2cpp-metadata.md#L149), [il2cpp-metadata.md](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/docs/il2cpp-metadata.md#L320). The relevant regression checks in [il2cppBinaryFile.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/il2cpp/il2cppBinaryFile.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3498
No description provided.