Universal Binaries

From Crypto++ Wiki
Jump to navigation Jump to search

Apple platforms support Universal Binaries. A Universal Binary is an executable or library with multiple CPU architectures. Universal Binaries are sometimes referred to as "fat binaries" or "fat libraries". This wiki page will show you how to create a Universal Binary with multiple architectures using Apple's lipo tool. The article is similar Build Multiarch OpenSSL on OS X on Stack Overflow. Also see Compiling for Multiple CPU Architectures and Universal Binary Programming Guidelines on the Apple Developer site.

The Crypto++ library is NOT safe to build Universal Binaries out of the box. The reason is, the Clang compiler does not accurately reflect the ISA using preprocessor macros, so config_asm.h is not accurate and plain wrong is some instances. For example, Arm64 lacks CRC32 and polynomial multiplies even though Clang and the processor support the instructions. Because of problems with config_asm.h, you often have to update config_asm.h manually or with configure.sh. configure.sh only handles one ISA, so it is not possible to generate a new config_asm.h with multiple CPU architectures.

configure.sh updates both config_asm.h and config_cxx.h. Even though we only need to fixup config_asm.h, the procedure below fixes both config_asm.h and config_cxx.h. config_cxx.h is a problem because Clang is also inaccurate with respect to C++ features. Also see Bug 39631, __has_feature(cxx_exceptions) returns true but fails to compile std::uncaught_exceptions().

The same procedure applies to other Apple platforms, like armv7 and arm64. ARM builds are a little trickier and require use of setenv-ios.sh to setup the cross-compile environment. They also require more unique names based on SDK. For example, both iPads and AppleTV are arm64, but they use different SDKs. Also see iOS (Xcode) and iOS (Command Line) wiki pages.

Universal Binary

The following is a procedure you can use to build an Universal Binary on OS X that include 32-bit i386 and 64-bit x86_64. The procedure uses an install or destination directory of cryptopp-universal. The working or source directory to build the artifacts is cryptopp.

The procedure below uses configure.sh to configure the library for the architecture under the Clang compiler. Before you begin you should copy configure.sh to the working directory. Detailed instructions on using the script are located at configure.sh.

$ cd cryptopp
$ cp TestScripts/configure.sh .
$ chmod u+x *.sh

32-bit i386

The first step is to build the library for i386 with an install directory of cryptopp-universal. Before building you have to reconfigure the library for i386 using configure.sh. You should use the same CXX and CXXFLAGS for this part of the configure and make steps.

$ CXX=clang++ CXXFLAGS="-DNDEBUG -g2 -O3 -arch i386" ./configure.sh
Configuring for x86
Compiler: /usr/bin/clang++
Linker: /usr/bin/ld

Once reconfigured build the library. Use the same CXX and CXXFLAGS for make.

$ CXX=clang++ CXXFLAGS="-DNDEBUG -g2 -O3 -arch i386" make -j 5
Using testing flags: -DNDEBUG -g2 -O3 -arch i386
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch i386 -c cryptlib.cpp
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch i386 -c cpu.cpp
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch i386 -c integer.cpp
...

Next, install the library at cryptopp-universal.

$ make install PREFIX=../cryptopp-universal
cp *.h ../cryptopp-universal/include/cryptopp
chmod 0644 ../cryptopp-universal/include/cryptopp/*.h
cp libcryptopp.a ../cryptopp-universal/lib
chmod 0644 ../cryptopp-universal/lib/libcryptopp.a
cp cryptest.exe ../cryptopp-universal/bin
chmod 0755 ../cryptopp-universal/bin/cryptest.exe
...

Once the library is installed you have to rename config_asm.h and config_cxx.h. This keeps the arch specific headers from being overwritten for the next architecture. The missing config_asm.h and config_cxx.h will be fixed later.

mv ../cryptopp-universal/include/cryptopp/config_asm.h ../cryptopp-universal/include/cryptopp/config_asm_i386.h
mv ../cryptopp-universal/include/cryptopp/config_cxx.h ../cryptopp-universal/include/cryptopp/config_cxx_i386.h

Once the headers are renamed the headers guards need to be fixed.

sed -i '' 's/CRYPTOPP_CONFIG_ASM_H/CRYPTOPP_CONFIG_ASM_i386_H/g' ../cryptopp-universal/include/cryptopp/config_asm_i386.h
sed -i '' 's/CRYPTOPP_CONFIG_CXX_H/CRYPTOPP_CONFIG_CXX_i386_H/g' ../cryptopp-universal/include/cryptopp/config_cxx_i386.h

Rename the library like the headers. The missing libcryptopp.a will be fixed later.

mv ../cryptopp-universal/lib/libcryptopp.a ../cryptopp-universal/lib/libcryptopp_i386.a

Rename the executable like the headers. The missing cryptest.exe will be fixed later.

mv ../cryptopp-universal/bin/cryptest.exe ../cryptopp-universal/bin/cryptest_i386.exe

Finally, clean the artifacts.

$ make distclean
...

64-bit x86_64

The second step is to build the library for x86_64 with an install directory of cryptopp-universal. Before building you have to reconfigure the library for x86_64 using configure.sh. You should use the same CXX and CXXFLAGS for this part of the configure and make steps.

$ CXX=clang++ CXXFLAGS="-DNDEBUG -g2 -O3 -arch x86_64" ./configure.sh
Configuring for x86_64
Compiler: /usr/bin/clang++
Linker: /usr/bin/ld

Once reconfigured build the library. Use the same CXX and CXXFLAGS for make.

$ CXX=clang++ CXXFLAGS="-DNDEBUG -g2 -O3 -arch x86_64" make -j 5
Using testing flags: -DNDEBUG -g2 -O3 -arch x86_64
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch x86_64 -c cryptlib.cpp
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch x86_64 -c cpu.cpp
clang++ -pthread -pipe -DNDEBUG -g2 -O3 -arch x86_64 -c integer.cpp
...

Next, install the library at cryptopp-universal.

$ make install PREFIX=../cryptopp-universal
cp *.h ../cryptopp-universal/include/cryptopp
chmod 0644 ../cryptopp-universal/include/cryptopp/*.h
cp libcryptopp.a ../cryptopp-universal/lib
chmod 0644 ../cryptopp-universal/lib/libcryptopp.a
cp cryptest.exe ../cryptopp-universal/bin
chmod 0755 ../cryptopp-universal/bin/cryptest.exe
...

Once the library is installed you have to rename config_asm.h and config_cxx.h. This keeps the arch specific headers from being overwritten for the next architecture. The missing config_asm.h and config_cxx.h will be fixed later.

mv ../cryptopp-universal/include/cryptopp/config_asm.h ../cryptopp-universal/include/cryptopp/config_asm_x86_64.h
mv ../cryptopp-universal/include/cryptopp/config_cxx.h ../cryptopp-universal/include/cryptopp/config_cxx_x86_64.h

Once the headers are renamed the headers guards need to be fixed.

sed -i '' 's/CRYPTOPP_CONFIG_ASM_H/CRYPTOPP_CONFIG_ASM_x86_64_H/g' ../cryptopp-universal/include/cryptopp/config_asm_x86_64.h
sed -i '' 's/CRYPTOPP_CONFIG_CXX_H/CRYPTOPP_CONFIG_CXX_x86_64_H/g' ../cryptopp-universal/include/cryptopp/config_cxx_x86_64.h

Rename the library like the headers. The missing libcryptopp.a will be fixed later.

mv ../cryptopp-universal/lib/libcryptopp.a ../cryptopp-universal/lib/libcryptopp_x86_64.a

Rename the executable like the headers. The missing cryptest.exe will be fixed later.

mv ../cryptopp-universal/bin/cryptest.exe ../cryptopp-universal/bin/cryptest_x86_64.exe

Finally, clean the artifacts.

$ make distclean
...

Combining artifacts

At this point all of the artifacts have been installed in cryptopp-universal. Some of the artifacts have an i386 suffix, and some of the artifacts have an x86_64 suffix. They need to be combined using lipo. The missing config_asm.h and config_cxx.h also need to be fixed.

The first step is fix the missing config_asm.h and config_cxx.h. Add a new config_asm.h and config_cxx.h with the contents shown below.

$ cat ../cryptopp-universal/include/cryptopp/config_asm.h
#ifndef CRYPTOPP_CONFIG_ASM_H
#define CRYPTOPP_CONFIG_ASM_H

#if __x86_64 || __x86_64__ || __amd64 || __amd64__
# include "config_asm_x86_64.h"
#elif __i386 || __i386__
# include "config_asm_i386.h"
#else
# error Unknown architecture
#endif

#endif // CRYPTOPP_CONFIG_ASM_H

And:

$ cat ../cryptopp-universal/include/cryptopp/config_cxx.h
#ifndef CRYPTOPP_CONFIG_CXX_H
#define CRYPTOPP_CONFIG_CXX_H

#if __x86_64 || __x86_64__ || __amd64 || __amd64__
# include "config_cxx_x86_64.h"
#elif __i386 || __i386__
# include "config_cxx_i386.h"
#else
# error Unknown architecture
#endif

#endif // CRYPTOPP_CONFIG_CXX_H

Next, use lipo to combine libcryptopp.a and cryptest.exe.

lipo -create \
    ../cryptopp-universal/lib/libcryptopp_i386.a \
    ../cryptopp-universal/lib/libcryptopp_x86_64.a \
    -output ../cryptopp-universal/lib/libcryptopp.a

lipo -create \
    ../cryptopp-universal/bin/cryptest_i386.exe \
    ../cryptopp-universal/bin/cryptest_x86_64.exe \
    -output ../cryptopp-universal/bin/cryptest.exe

Verifying artifacts

You can use lipo to verify the universal binariues.

$ lipo -info ../cryptopp-universal/lib/libcryptopp.a
Architectures in the fat file: ../cryptopp-universal/lib/libcryptopp.a are: i386 x86_64

$ lipo -info ../cryptopp-universal/bin/cryptest.exe
Architectures in the fat file: ../cryptopp-universal/bin/cryptest.exe are: i386 x86_64

You can also run cryptest.exe under each architecture. Notice sizeof(word) == 4 on i386.

$ cd cryptopp-universal/bin
$ arch -i386 ./cryptest.exe v
Using seed: 1608249965      

Testing Settings...

passed:  Your machine is little endian.
passed:  Aligned data access.
passed:  sizeof(byte) == 1
passed:  sizeof(word16) == 2
passed:  sizeof(word32) == 4
passed:  sizeof(word64) == 8
passed:  sizeof(hword) == 2, sizeof(word) == 4, sizeof(dword) == 8
passed:  cacheLineSize == 64
hasSSE2 == 1, hasSSSE3 == 1, hasSSE4.1 == 1, hasSSE4.2 == 0, hasAVX == 0, hasAVX
2 == 0, hasAESNI == 0, hasCLMUL == 0, hasRDRAND == 0, hasRDSEED == 0, hasSHA == 
0, isP4 == 0
...

And x86_64. Notice sizeof(word) == 8 on x86_64.

$ cd cryptopp-universal/bin
$ arch -x86_64 ./cryptest.exe v
Using seed: 1608250036      

Testing Settings...

passed:  Your machine is little endian.
passed:  Aligned data access.
passed:  sizeof(byte) == 1
passed:  sizeof(word16) == 2
passed:  sizeof(word32) == 4
passed:  sizeof(word64) == 8
passed:  sizeof(word128) == 16
passed:  sizeof(hword) == 4, sizeof(word) == 8, sizeof(dword) == 16
passed:  cacheLineSize == 64
hasSSE2 == 1, hasSSSE3 == 1, hasSSE4.1 == 1, hasSSE4.2 == 0, hasAVX == 0, hasAVX
2 == 0, hasAESNI == 0, hasCLMUL == 0, hasRDRAND == 0, hasRDSEED == 0, hasSHA == 
0, isP4 == 0
...

Dynamic library

Crypto++'s default make rule does not build a dynamic library. The default recipe in GNUmakefile only builds cryptest.exe and libcryptopp.a:

LINK_LIBRARY ?= libcryptopp.a
...

.PHONY: default
default: cryptest.exe
...

cryptest.exe: $(LINK_LIBRARY) $(TESTOBJS)
    $(CXX) -o $@ ...

If you want to build libcryptopp.dylib with cryptest.exe and libcryptopp.a, then issue make all:

.PHONY: all static dynamic
all: static dynamic cryptest.exe

You will also need to rename the dynamic libraries, and then combine them using lipo:

lipo -create \
    ../cryptopp-universal/lib/libcryptopp_i386.dylib \
    ../cryptopp-universal/lib/libcryptopp_x86_64.dylib \
    -output ../cryptopp-universal/lib/libcryptopp.dylib

Downloads

No downloads available.