Eyes, JAPAN Blog > What is an universal binary?

What is an universal binary?



When I build binaries from given source code on Mac, I’m trying to achieve as much compatibility as possible. In this case, I mean 32 and 64bit modes.

Snow Leopard runs 64-bit-capable applications in 64-bit mode regardless of whether it’s booting into a 64-bit or 32-bit kernel. It means, if hardware is capable of running application in 64bit mode and binaries itself was compiled for 64bit architechture, you will have a performance boost. There are two cases, why there might be a boost:

  • 64-bit computing is necessary if application is able to access to more than 4GB of RAM (usually this would be professional development tools for image, video and music processing)
  • intel processors have built-in math routines that operate more efficiently in 64-bit mode, thus tasks are processed in fewer steps, which means that certain math-intensive tasks will see a speed boost under 64bit mode.

On the other hand, however, if application is compiled as 32bit only it will be compatible with all system, that runs on inter-platforms.

To be able to build application binaries and then necessary for each mode, Apple uses so called “fat” or universal binaries.

A universal binary is, in Apple parlance, an executable file or application bundle that runs natively on either PowerPC or Intel-based Macintosh computers; it is an implementation of the concept more generally known as a fat binary.

With the release of Mac OS X Snow Leopard, and before that, since the move to 64-bit architectures in general, some software publishers such as Mozilla have used the term Universal to refer to a fat binary that includes tailored builds for both i386 (32-bit Intel) and x86_64 systems. The same mechanism which is used to select between the custom PowerPC or Intel builds of an application, is also used to select between the 32-bit or 64-bit builds of either PowerPC or Intel architectures.

The universal binary format was introduced at the 2005 Apple Worldwide Developers Conference as a means to ease the transition from the existing PowerPC architecture to systems based on Intel processors, which began shipping in 2006. Universal binaries typically include both PowerPC and x86 versions of a compiled application. The operating system detects a universal binary by its header, and executes the appropriate section for the architecture in use. This allows the application to run natively on any supported architecture, with no negative performance impact beyond an increase in the storage space taken up by the larger binary.

In practice, “file” can show architecture of given binary file:

$: file boxes
boxes: Mach-O executable i386

In case of other unix-based system, like FreeBSD, it will show something like this:

$: file boxes
boxes: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), for FreeBSD 8.0 (800107), stripped

And that’s how the real mac universal binary would look like:

$: file boxes
boxes: Mach-O universal binary with 2 architectures
boxes (for architecture i386):	Mach-O executable i386
boxes (for architecture x86_64):	Mach-O 64-bit executable x86_64

Now let’s do some practical example of how it is possible to make an universal binary by hands.

  1. First of all, we have to compile our binaries for a specific architecture, i.e. if we would want to support both 32 and 64bit modes, we have to compile at least twice
  2. After binaries for each architechture was successfully compiled, we use “lipo” tool to combine two binaries into one

For this example, I’m (as usual) using boxes application. It is has only one binary file and it doesn’t take much time to compile.

Let’s do this.

  1. Using sources with necessary ARCHFLAGS and compiling 32 bit binary. This is how file would look like:
  2. $: file 32bit/boxes
    32bit/boxes: Mach-O executable i386
  3. Compile 64bit binary (ARCHFLAGS is x86_64):
  4. $: file 64bit/boxes
    64bit/boxes: Mach-O 64-bit executable x86_64
  5. Now we finally create universal binary:
  6. $: lipo 32bit/boxes 64bit/boxes -create -output boxes
  7. Checking
  8. $: file boxes
    boxes: Mach-O universal binary with 2 architectures
    boxes (for architecture i386):	Mach-O executable i386
    boxes (for architecture x86_64):	Mach-O 64-bit executable x86_64
  9. We can also have a detailed view on the binary contents:
  10. $: lipo -detailed_info boxes
    Fat header in: boxes
    fat_magic 0xcafebabe
    nfat_arch 2
    architecture i386
        cputype CPU_TYPE_I386
        cpusubtype CPU_SUBTYPE_I386_ALL
        offset 4096
        size 82020
        align 2^12 (4096)
    architecture x86_64
        cputype CPU_TYPE_X86_64
        cpusubtype CPU_SUBTYPE_X86_64_ALL
        offset 90112
        size 86444
        align 2^12 (4096)
  11. Finally, notice file size:
  12. 32bit:		-rwxr-xr-x   1 denvazh  staff   82020 Aug  1 14:27 boxes
    64bit:		-rwxr-xr-x   1 denvazh  staff   86444 Aug  1 14:26 boxes
    universal:	-rwxr-xr-x   1 denvazh  staff  176556 Aug  1 14:29 boxes

    As you might notice, creating fat binaries itself is not so diffictult, however for a bigger application it necessary to use more advanced tool, such as makefile, to handle the same pattern as above, but for a huge sets of files.
    Hopefully, from OS X Lion Apple dropped support for 32bit application so building universal applications for ultimate compatibility is no longer necessary.

  • このエントリーをはてなブックマークに追加

Comments are closed.