Stripping debug info from a Mach-O dylib with llvm-objcopy --strip-debug can produce a dylib with misaligned __LINKEDIT entries, e.g. the __LINKEDIT string pool offset is not aligned to the pointer size.
This is inconsistent with the behavior of Apple's ld64 nor lld-macho, where every __LINKEDIT entry is aligned to the pointer size (except for the code signature, which is aligned to 16 bytes).
ld64 behavior: https://github.com/apple-oss-distributions/ld64/blob/ld64-957.1/src/ld/LinkEditClassic.hpp#L63
lld-macho behavior: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.7/lld/MachO/SyntheticSections.h#L62, see https://reviews.llvm.org/D94935
On macOS 27.0, dyld rejects dylibs with misaligned __LINKEDIT entries, revealing this issue.
(See rust-lang/rust#157750.)
Reproducer:
$ cat addr_puts.c
#include <stdint.h>
#include <stdio.h>
uintptr_t answer(void) { return (uintptr_t)&puts; }
$ clang -dynamiclib -g -O0 addr_puts.c -o libaddr_puts.dylib
$ cp libaddr_puts.dylib libaddr_puts.stripped.dylib
$ llvm-objcopy --strip-debug libaddr_puts.stripped.dylib
When looking at the __LINKEDIT entry offsets in the dylib:
$ otool -l libaddr_puts.dylib | grep 'symoff\|nsyms\|indirectsymoff\|nindirectsyms\|stroff'
symoff 32888
nsyms 11
stroff 33072
extrefsymoff 0
indirectsymoff 33064
nindirectsyms 1
$ otool -l libaddr_puts.stripped.dylib | grep 'symoff\|nsyms\|indirectsymoff\|nindirectsyms\|stroff'
symoff 32888
nsyms 2
stroff 32924
extrefsymoff 0
indirectsymoff 32920
nindirectsyms 1
From the original dylib, the size of the symbol table is sizeof(nlist_64)*nsyms + 4*nindirectsyms = 180, therefore the symbol table is padded 4 bytes and the string table is aligned to 8 bytes, and placed at 33072.
However from the stripped dylib, the size of the symbol table is sizeof(nlist_64)*nsyms + 4*nindirectsyms = 36, so the string table is placed at 32924, misaligned. Therefore dlopen-ing the stripped dylib fails:
$ python3 -c 'import sys, ctypes; sys.tracebacklimit=0; ctypes.CDLL("libaddr_puts.dylib"); print("ok")'
ok
$ python3 -c 'import sys, ctypes; sys.tracebacklimit=0; ctypes.CDLL("libaddr_puts.stripped.dylib"); print("ok")'
OSError: dlopen(libaddr_puts.stripped.dylib, 0x0006): tried: 'libaddr_puts.stripped.dylib' (mis-aligned LINKEDIT string pool, fileOffset=0x0000809C), '/System/Volumes/Preboot/Cryptexes/OSlibaddr_puts.stripped.dylib' (no such file), '/usr/lib/libaddr_puts.stripped.dylib' (no such file, not in dyld cache), 'libaddr_puts.stripped.dylib' (mis-aligned LINKEDIT string pool, fileOffset=0x0000809C)
While not included in the reproduction, stripping with /usr/bin/strip also places the string table aligned to 8 bytes.
Stripping debug info from a Mach-O dylib with
llvm-objcopy --strip-debugcan produce a dylib with misaligned__LINKEDITentries, e.g. the__LINKEDITstring pool offset is not aligned to the pointer size.This is inconsistent with the behavior of Apple's ld64 nor lld-macho, where every
__LINKEDITentry is aligned to the pointer size (except for the code signature, which is aligned to 16 bytes).ld64 behavior: https://github.com/apple-oss-distributions/ld64/blob/ld64-957.1/src/ld/LinkEditClassic.hpp#L63
lld-macho behavior: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.7/lld/MachO/SyntheticSections.h#L62, see https://reviews.llvm.org/D94935
On macOS 27.0, dyld rejects dylibs with misaligned
__LINKEDITentries, revealing this issue.(See rust-lang/rust#157750.)
Reproducer:
When looking at the __LINKEDIT entry offsets in the dylib:
From the original dylib, the size of the symbol table is
sizeof(nlist_64)*nsyms + 4*nindirectsyms = 180, therefore the symbol table is padded 4 bytes and the string table is aligned to 8 bytes, and placed at 33072.However from the stripped dylib, the size of the symbol table is
sizeof(nlist_64)*nsyms + 4*nindirectsyms = 36, so the string table is placed at 32924, misaligned. Thereforedlopen-ing the stripped dylib fails:While not included in the reproduction, stripping with
/usr/bin/stripalso places the string table aligned to 8 bytes.