Binário Universal

O que é?

O Mac OS X utiliza o formato Mach-O (Mach Object) para binários. Um binário do tipo Universal Binary (Binário Universal) ou também chamado de Fat Binary (Binário Gordo) na época do NeXTStep, é um formato de arquivo executável que pode conter código de uma ou mais arquiteturas.

Vale comentar que até o Leopard, o Mac OS X inclua código para executar tanto em uma máquina Intel (i386) quanto em um PowerPC (ppc). O Snow Leopard marcou o fim do suporte a arquitetura PowerPC, desta forma os binários dessa versão são apenas para arquitetura Intel de 32 e 64 bits.

$ lipo -i /bin/ls 
Architectures in the fat file: /bin/ls are: x86_64 i386 

Mesmo com esta mudança de rumo do Mac OS X, ainda é possível encontrar binários com três arquiteturas distintas, duas Intel e um PowerPC, como por exemplo no código do XCode.

$ lipo -i /Developer/Applications/Xcode.app/Contents/MacOS/Xcode 
Architectures in the fat file:
	/Developer/Applications/Xcode.app/Contents/MacOS/Xcode are:
		x86_64 i386 ppc7400

Tipicamente um compilador gera por padrão o código para uma CPU (target). Vejamos como o GCC se comporta sem passar opções especiais a ele.

$ cat hello.c 
main() { puts("Hello World!"); }

$ gcc hello.c -o hello
$ ./hello 
Hello World!

$ file hello
hello: Mach-O executable i386
$ lipo -i hello
Non-fat file: hello is architecture: i386

O GCC gera código para a arquitetura corrente da máquina, se estivesse no Mac OS X em 64 bits o resultado seria x86_64. Note que este binário não é universal (Non-fat).

Construindo um binário Universal

No XCode a opção de compilar código Universal é selecionável por configuração. Para o GCC, os parâmetros para construir um binário universal são -arch i386 e -arch x86_64, conforme mostra o exemplo:

$ gcc -arch i386 -arch x86_64 hello.c -o hello
$ lipo -i hello
Architectures in the fat file: hello are: i386 x86_64

Com este binário, pode-se executá-lo tanto em Intel 32 bits (i386) ou Intel 64 bits (x86_64), o código está disponível para ambas as arquiteturas no arquivo pois é universal. Na prática, nem sempre é possível construir diretamente um binário universal apenas passando os parâmetros -arch.

Uma forma alternativa de gerar um binário universal está em compilar o fonte em diversas arquiteturas separadas (em arquivos) e depois juntar as partes em um único arquivo executável, através do utilitário lipo.

$ gcc -arch i386 hello.c -o hello-i386
$ gcc -arch x86_64 hello.c -o hello-x86_64
$ lipo -create -arch i386 hello-i386 -arch x86_64 hello-x86_64 -o hello
$ lipo -i hello
Architectures in the fat file: hello are: i386 x86_64 

Em projetos de software da vida real, deve modificar as variáveis CFLAGS, CXXFLAGS e LDFLAGS para incluir as opções de arch, algo como CFLAGS="-arch i386 -arch x86_64 -Os" LDFLAGS="-arch i386 -arch x86_64" para obter um binário universal.

Muitas vezes os projetos que utilizam autotools (o popular configure) disponibilizam uma opção para habitilar a construção de universal, algo como: --enable-universal, --enable-nextstep ou enable-darwin, que nada mais faz do que configurar as variávies FLAGS apresentadas.

©2009 Rudá Moura