43

When using Clang's or GCC's Darwin backends to create executables for OSX, the flag -mmacosx-version-min=version can be used to set the earliest version of OSX the executable will run on.

Is there any way to trace back from a given executable which flag was used to compile it? I.e. is there a way to determine which minimum OSX version is targeted by a given executable?

earl
  • 40,327
  • 6
  • 58
  • 59
  • 1
    For the record, Apple haven’t contributed to GCC since its licence was changed to GPLv3. Whenever possible, use Clang/LLVM. –  Jun 17 '13 at 13:35
  • 1
    Short answer `otool -l /path/to/bin | grep -E -A4 '(LC_VERSION_MIN_MACOSX|LC_BUILD_VERSION)' | grep -B1 sdk`; this also works with ARM64 machines like M1. `version` or `minos` gives the minimum supported macOS version, while `sdk` gives SDK version. – legends2k Aug 20 '21 at 06:02

3 Answers3

64

Use otool -l /path/to/binary and inspect the LC_VERSION_MIN_MACOSX load command; specifically, the version field.

For example, a binary compiled with the 10.8 SDK with deployment target (-mmacosx-version-min) 10.8 should have an LC_VERSION_MIN_MACOSX like this:

Load command 9
      cmd LC_VERSION_MIN_MACOSX
  cmdsize 16
  version 10.8
      sdk 10.8

whereas a binary compiled with the 10.8 SDK with deployment target 10.7 should have an LC_VERSION_MIN_MACOSX load command like this:

Load command 9
      cmd LC_VERSION_MIN_MACOSX
  cmdsize 16
  version 10.7
      sdk 10.8
  • 2
    Is there any way to get this information for static libraries (".a" files)? – j b Apr 13 '14 at 17:14
  • 2
    This works also for static libraries (.a files). You'll see such load command per each object in the library. There's a nice UI application for Mac called 'MachOView" that allows comfortable browsing of binaries, and easy searching for these load commands. – Motti Shneor Sep 05 '16 at 14:00
  • wow, thanks! however, is there a way to find out this information with the stock tools built into macOS? 'otool' is part of the Developer Tools – user1259710 Jan 28 '19 at 21:28
  • is there a library or system api to do this programatically? – electronic_coder Jan 20 '20 at 12:14
  • @electronic_coder Yes, there is an API to do it programmatically. Look at the source of otool (https://github.com/opensource-apple/cctools/tree/master/otool). – prewett Mar 25 '20 at 19:51
  • This is a great solution. I'm using it in my build scripts since a while. Unfortunately it seems that it doesn't work with arm64 (Apple Silicon) binaries. I'm using `otool -l -arch x86_64 /path/to/binary` to get the minimum version of my fat binary. – ndreisg Jan 05 '21 at 16:31
15

The load command that is mentioned in the accepted answer is not listed when I build a modern macOS executable. However LC_BUILD_VERSION does contain the minos and sdk fields:

Load command 10
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform 1
    minos 11.0
      sdk 11.1
   ntools 1
     tool 3
  version 609.8
prideout
  • 2,895
  • 1
  • 23
  • 25
  • I noticed this too. See this screenshot with both types of output, from binaries that were built in the same CI run but by different tools (PyInstaller vs Rust): https://github.com/ActivityWatch/activitywatch/pull/544#issuecomment-751801675 – erb Dec 28 '20 at 17:52
  • 3
    The reason why you don't see the `LC_VERSION_MIN_MACOSX` is probably because your binary is for architecture arm64 or universal and you have an Apple Silicon Mac. Note that in case of a universal binary the `minos` field does not necessarily reflect the actual minimum SDK if it is lower than 11.0. Use `otool -l -arch x86_64 /path/to/binary` for universal binaries. – ndreisg Jan 05 '21 at 16:44
  • 1
    Ah, it is true that I'm using Apple Silicon. – prideout Jan 05 '21 at 19:17
  • I'm not seeing it either, on intel with big sur – CiNN Jan 06 '21 at 22:47
  • ok looks like if i build with CFLAGS=-mmacosx-version-min=10.10 I do see the LC_VERSION_MIN_MACOSX in the binary, and I lose LC_BUILD_VERSION – CiNN Jan 07 '21 at 01:37
6

An alternative to using otool | grep is to use vtool (available on macOS 10.15 and later).

For example:

❯ vtool -show-build ./test
test (architecture x86_64):
Load command 10
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform MACOS
    minos 12.0
      sdk 12.3
   ntools 1
     tool LD
  version 764.0
test (architecture arm64):
Load command 10
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform MACOS
    minos 12.0
      sdk 12.3
   ntools 1
     tool LD
  version 764.0

As noted in the comments, this does not work on static libraries or object files. From man vtool:

     Currently vtool only operates on final linked binaries, such as executable files, dynamic libraries, and bundles. Because the
     executable code in Mach-O final linked binaries cannot be moved or resized, and because the load commands reside between the mach
     header and the executable code, there is only a limited amount of space available for vtool to save changes. Set operations that
     add or resize load commands may fail if there isn't enough space in the Mach-O file availble to hold the new load commands.

vtool does, however, let you edit the build and source versions in the mach header, provided there is enough space for your revisions.

  • This seems not to work on static libraries, compared to the answers using `otool -l` that work for me with static libraries (on Intel, macOS 12.5, XCode 13). – emmenlau Aug 02 '22 at 08:15
  • 1
    Yes. This is documented in `man vtool`. Apologies for omitting it from my answer. In exchange, you get the ability to set or remove the relevant load commands, provided enough space in the mach header. – Theoretical Economist Aug 02 '22 at 08:20
  • This is better than looking at `LC_BUILD_VERSION` in `otool` output, because `vtool` decodes the `platform` and `tool` numbers into human-readable strings. – rob mayoff Nov 04 '22 at 20:01