Skip to content

[llvm-objcopy][MachO] Stripping debug info from dylib produces misaligned __LINKEDIT entries #203678

Description

@goranmoomin

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions