Skip to content

Fix kalam.dylib chained-fixups seg_count so OpenMTP launches on newer macOS#468

Open
northgate-dev wants to merge 1 commit into
ganeshrvel:masterfrom
northgate-dev:fix-kalam-chained-fixups-segcount
Open

Fix kalam.dylib chained-fixups seg_count so OpenMTP launches on newer macOS#468
northgate-dev wants to merge 1 commit into
ganeshrvel:masterfrom
northgate-dev:fix-kalam-chained-fixups-segcount

Conversation

@northgate-dev

Copy link
Copy Markdown

Problem

OpenMTP 3.2.25 crashes instantly at startup on recent macOS versions with:

Uncaught Exception:
Error: Failed to load shared library: dlopen(.../Contents/Resources/bin/arm64/kalam.dylib, 0x0006):
  '...kalam.dylib' (chained fixups, seg_count does not match number of segments)

Likely the same root cause as the startup crashes reported in #412.

Root cause

The Go toolchain that built kalam.dylib emitted a dyld_chained_starts_in_image header declaring seg_count = 4, but the binaries actually contain 5 segments: __TEXT, __DATA_CONST, __DATA, __LINKEDIT, plus the trailing __DWARF debug segment Go appends after __LINKEDIT. Older versions of dyld ignored the mismatch; newer macOS validates it strictly and refuses to load the library, which takes the whole app down before the UI appears.

You can see the defect on the current master binaries:

$ xcrun dyld_info -fixups build/mac/bin/arm64/kalam.dylib
build/mac/bin/arm64/kalam.dylib [arm64]:
   chained fixups, seg_count does not match number of segments

Fix

A 4-byte patch per binary: bump seg_count from 4 to 5, with the new seg_info_offset[4] entry set to 0 ("no fixups in this segment", correct for __DWARF), written into the existing zero padding before the first dyld_chained_starts_in_segment blob — so no other offsets shift. Both arm64 and amd64 dylibs are patched and ad-hoc re-signed. The medieval/amd64 build uses classic relocations (no LC_DYLD_CHAINED_FIXUPS) and is unaffected.

Verified after patching:

  • xcrun dyld_info -fixups parses both binaries cleanly
  • dlopen of the patched arm64 dylib succeeds on a macOS version that rejected the original
  • A locally repacked OpenMTP.app with the patched dylib launches and runs normally
Script used to apply the patch (for review/reproduction)
#!/usr/bin/env python3
import struct, sys

LC_SEGMENT_64 = 0x19
LC_DYLD_CHAINED_FIXUPS = 0x80000034

def patch(path):
    d = bytearray(open(path, 'rb').read())
    magic, = struct.unpack_from('<I', d, 0)
    assert magic == 0xfeedfacf, f'{path}: not a 64-bit little-endian Mach-O'
    ncmds, = struct.unpack_from('<I', d, 16)
    off, nsegs, fixups_off = 32, 0, None
    for _ in range(ncmds):
        cmd, cmdsize = struct.unpack_from('<2I', d, off)
        if cmd == LC_SEGMENT_64:
            nsegs += 1
        elif cmd == LC_DYLD_CHAINED_FIXUPS:
            fixups_off, = struct.unpack_from('<I', d, off + 8)
        off += cmdsize
    assert fixups_off is not None, f'{path}: no LC_DYLD_CHAINED_FIXUPS'
    starts_offset, = struct.unpack_from('<I', d, fixups_off + 4)
    so = fixups_off + starts_offset
    seg_count, = struct.unpack_from('<I', d, so)
    if seg_count == nsegs:
        print(f'{path}: seg_count already {nsegs}, nothing to do')
        return
    assert seg_count < nsegs
    offsets = struct.unpack_from(f'<{seg_count}I', d, so + 4)
    first_info = min(o for o in offsets if o)
    need_end = 4 + 4 * nsegs
    assert need_end <= first_info, f'{path}: no room to grow offsets array'
    assert all(b == 0 for b in d[so + 4 + 4 * seg_count : so + need_end])
    struct.pack_into('<I', d, so, nsegs)
    open(path, 'wb').write(d)
    print(f'{path}: seg_count {seg_count} -> {nsegs}')

for p in sys.argv[1:]:
    patch(p)

Long-term fix

Rebuilding the kalam kernel with a recent Go toolchain (or stripping DWARF from the c-shared output) should produce correct chained-fixups metadata at the source; this PR patches the vendored binaries so the shipped app works on current macOS in the meantime.

🤖 Generated with Claude Code

The Go linker emitted a dyld_chained_starts_in_image header in
kalam.dylib (arm64 and amd64) declaring seg_count = 4, while the
binaries actually contain 5 segments (__TEXT, __DATA_CONST, __DATA,
__LINKEDIT, plus the trailing __DWARF debug segment the Go toolchain
appends). Older dyld ignored the mismatch; newer macOS validates it
strictly and refuses to load the library, so OpenMTP crashes at
startup with:

  dlopen(.../kalam.dylib, 0x0006): chained fixups, seg_count does
  not match number of segments

This patches seg_count from 4 to 5 in both dylibs, with the new
seg_info_offset entry set to 0 (no fixups in __DWARF) written into
the existing zero padding before the first starts_in_segment blob —
a 4-byte change per binary, then ad-hoc re-signed. Verified with
`xcrun dyld_info -fixups` and a successful dlopen on a macOS version
that previously rejected the libraries.

The medieval/amd64 build uses classic relocations (no
LC_DYLD_CHAINED_FIXUPS) and is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ganeshrvel

Copy link
Copy Markdown
Owner

What os version, and app version are you facing this issue?

@ganeshrvel

Copy link
Copy Markdown
Owner

Are you on golden gate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants