otool doesn't list "Load" commands?

I'm struggling with compiling lib opus so that it works in the simulator on Apple silicon. I found a thread on the forums that seems to address part of the issue, but I am unable to build the static lib so that it shows the platform it is targeting.

The thread mentions that I should be able to run otool and see a "load commands" that indicate the platform.

When I run otool against the static library that we have created, it doesn't list any load commands. I don't see LC_BUILD_VERSION or LC_VERSION_MIN_***. Why would there not be any "Load command" entries?

% otool -l -arch arm64 dependencies/lib/libopus.a
Archive : dependencies/lib/libopus.a
dependencies/lib/libopus.a(bands.o): is an LLVM bit-code file
dependencies/lib/libopus.a(celt.o): is an LLVM bit-code file
dependencies/lib/libopus.a(celt_encoder.o): is an LLVM bit-code file
dependencies/lib/libopus.a(celt_decoder.o): is an LLVM bit-code file
...
dependencies/lib/libopus.a(mlp.o): is an LLVM bit-code file
dependencies/lib/libopus.a(mlp_data.o): is an LLVM bit-code file

The static library has the two architectures embedded in it, but when compiling the framework for the simulator platform the linking phase complains that we are building for the simulator, but linking object code built for ios.

% lipo -info dependencies/lib/libopus.a
Architectures in the fat file: dependencies/lib/libopus.a are: x86_64 arm64 

In case you are curious, I'm just piggybacking on this project that has a build-libopus.sh script in the root directory that builds the official open source Opus library files. My hope is to build this static library for ios, ios-simulator, and mac-catalyst platforms and then include them in a xcframework.

Replies

First up, I want to point you at An Apple Library Primer. It explains a lot of arcane terminology and concepts that I’m going to assume.

Static libraries contain an archive of Mach-O object files. They are not Mach-O files in and of themselves.

That can, however, be universal binaries, containing multiple architecture-specific archives. Annoyingly, this has the same .a extension as a non-universal one.

If you point otool at a universal static library it prints info about each architecture:

% otool -l libWaffle.a 
Archive : libWaffle.a (architecture arm64)
libWaffle.a(Waffle.o) (architecture arm64):
Load command 0
      cmd LC_SEGMENT_64
…
Archive : libWaffle.a (architecture x86_64)
libWaffle.a(Waffle.o) (architecture x86_64):
Load command 0
      cmd LC_SEGMENT_64
…

Within each architecture you should find one of the various load commands that defines the platform. In my case that’s the LC_BUILD_VERSION command:

Load command 1
      cmd LC_BUILD_VERSION
  cmdsize 24
 platform 7
    minos 17.2
      sdk 17.2
   ntools 0

Note If you target older systems you might see the legacy LC_VERSION_MIN_ppp command, where ppp is for the platform.

For standard platforms otool will print a nice version of the platform number; for simulators you have to look it up. In this case, 7 is PLATFORM_IOSSIMULATOR, as defined in <mach-o/loader.h>.


As to what’s actually happening with your example, I’m not 100% sure. Your static library contains bitcode. Clearly the bitcode has embedded the platform info somewhere, but I don’t have enough experience with it to explain where. However, I will say that bitcode is now deprecated, so the easiest path forward would be to change your build system to create native code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"