Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Building an Open Source Universal Binary

Mac OS X includes many open source projects that contribute to the stability and robustness of the system. While Apple provides working versions of these for both PowerPC and Intel architectures, sometimes you want to build your own to tune performance or enable custom features. Plus, distributing a single binary is often preferable to keeping track of separate, architecture-specific binaries. These objectives can be accomplished by building the project as a Universal Binary, a file that contains code for both the PowerPC and Intel architectures.

OpenSSL is one such open source project. It provides an implementation of the Secure Sockets Layer (v2 and v3) and Transport Layer Security (v1), and has been included in Darwin since Mac OS X v10.0. This article shows how to use Xcode (we are using Xcode 2.2.1) to construct a make-based project that builds OpenSSL as a Universal Binary. We look at what needs to be done in order to get both the libraries and test applications to build and run properly. This should give you the information you need to start porting your own or other open source applications to Mac OS X.

You can download the Xcode project .DMG file (100KB), created for this article.

The OpenSSL Project

OpenSSL is an open source version of the Secure Sockets Layer library. You've seen the lock icon that appears in the corner of a web browser window when you connect to an https:// URL. That lock indicates a secure connection, and SSL is the underlying mechanism that makes it happen.

The openssl command-line tool man page provides an overview of the capabilities of the OpenSSL libraries:

  • Creation of RSA, DH and DSA key parameters
  • Creation of X.509 certificates, CSRs and CRLs
  • Calculation of Message Digests
  • Encryption and Decryption with Ciphers
  • SSL/TLS Client and Server Tests
  • Handling of S/MIME signed or encrypted mail

The OpenSSL project is moderately complex, but as you will see in our discussion it requires no source code changes to generate the core libraries. We will focus on how to build the project for both the PowerPC and Intel architectures using Xcode. We build the Universal Binary on a PowerPC Macintosh, but test on both PowerPC and Intel-based Macintoshes. You can also do it the other way around if you have an Intel-based Mac available. If you are new to the idea of multiple architectures, or open source projects in general, this may be a good way to get started.

The OpenSSL Project Structure

The first step is to download and unpack the OpenSSL source code archive. Double-click on the openssl-0.9.8a.tar.gz file to unarchive it. This article uses version 0.9.8a. Figure 1 shows the top-level folder structure of OpenSSL after it has been expanded. Our discussion will focus on the folders named crypto and ssl, and the resulting libraries built from the source files in those folders. The top-level makefile ("Makefile") will be used to guide our project settings.

OpenSSL Project Folder Hierarchy

Figure 1: OpenSSL Project Folder Hierarchy

Most open source projects use a makefile to control the build process. The makefile sets flags that instruct the compiler and linker which source files to include, what build options to use, and the type of the final binary. A discussion of makefiles is included in several ADC articles and documents that are referenced at the end of this article.

If you open the top-level makefile, you will see that it does not include platform-specific settings. In order to fill-in some of those details, you need to run the config shell script, which is included at the top-level. config analyzes your system and generates a makefile that reflects the system it is being run on. If you wish to change the behavior of config, you can run the Configure shell script and pass flags to it that describe how you would like the project built. You may elect to run config and Configure before beginning the Xcode project. It won't cause any problems, and you may find it instructional to see how the build system gets set up for running the make command from the command-line.

Listing 1 contains a portion of the generated makefile from the root folder of the OpenSSL project. The comment at the very top let's us know that Configure generated this file.

Listing 1: Top-Level Makefile

### Generated automatically from Makefile.org by Configure.

##
## Makefile for OpenSSL
##

VERSION=0.9.8a
MAJOR=0
MINOR=9.8

PLATFORM=darwin-ppc-cc
OPTIONS= no-gmp no-krb5 no-mdc2 no-rc5 no-shared no-zlib no-zlib-dynamic
CONFIGURE_ARGS=darwin-ppc-cc
SHLIB_TARGET=darwin-shared

CC= cc
CFLAG= -DOPENSSL_THREADS -D_REENTRANT -O3 -DB_ENDIAN

AR=ar $(ARFLAGS) r
RANLIB= /usr/bin/ranlib

DIRS=   crypto ssl engines apps test tools

LIBS=   libcrypto.a libssl.a

BUILD_CMD=  if [ -d "$$dir" ]; then \
	    (	cd $$dir && echo "making $$target in $$dir..." && \
		$(CLEARENV) && $(MAKE) -e $(BUILDENV) TOP=.. DIR=$$dir $$target \
	    ) || exit 1; \
	    fi

build_all: build_libs build_apps build_tests build_tools

build_libs: build_crypto build_ssl build_engines

build_crypto:
	@dir=crypto; target=all; $(BUILD_CMD)
build_ssl:
	@dir=ssl; target=all; $(BUILD_CMD)
build_engines:
	@dir=engines; target=all; $(BUILD_CMD)

What interests us in this makefile are the targets, specifically build_libs, and the CC variable value. Later, when we build the Intel library, we will override the CC setting.

Summary of the Build Sequence

The overall build sequence is this: build a PowerPC library, build an Intel library, then combine them into one library. OpenSSL also includes some test applications that we build in the final step. Below are the detailed steps. Note that we build each library (crypto and ssl) for each architecture.

  1. Build PPC: Create the PowerPC libraries
    • Clean out binaries
    • Setup the build environment for the native architecture (here, PPC)
    • Create folders to temporarily hold the libraries
    • Create the PPC versions of the libraries
    • Copy the PPC libraries to the temp folders
  2. Build i386: Create the Intel architecture libraries
    • Clean out binaries
    • Setup the build environment for the Intel architecture
    • Create the Intel versions of the libraries
    • Copy the Intel libraries to the temp folders
  3. Create the Universal Binaries
    • Use lipo to create a Universal crypto lib
    • Use lipo to create a Universal ssl lib
    • Copy the Universal libraries to the root folder. This is important because the test applications that are included with the OpenSSL archive look for the built libraries in the root folder.
  4. Create the test applications
    • Execute ranlib to rebuild the table of contents for each library
    • Setup the build environment for the native architecture
    • Create the PPC versions of the test applications

This general plan may be used with other open source projects. We will use shell scripts to perform the build steps.

Creating The Xcode Project

Now that we have taken a cursory glance at the OpenSSL archive structure and the desired set of build steps, we can start the Xcode project process. If you prefer to download the finished Xcode project, use the link provided in the "For More Information" section at the end of this article. Our first step is to create a new project. In Xcode, select File > New Project.... Then, in the Assistant dialog, choose project type "Empty Project", as shown in Figure 2.

Create a New Xcode Project

Figure 2: Create A New Xcode Project

Name the project "OpenSSL", and create it inside the OpenSSL folder, as shown in Figure 3. Note that the default Xcode behavior is to create a subfolder named after the target. We do not want that behavior here, because the build scripts are intended to run at the top level folder of the project.

Create The Project Directly In The OpenSSL Folder

Figure 3: Create The Project Directly In The OpenSSL Folder

The next step is to add targets that perform the build steps. Right-click the "Targets" group of the project, then Add > New Target... as shown in Figure 4.

Add A Target

Figure 4: Add A Target

Select the "Aggregate" target type, as shown in Figure 5.

Select A Target Type

Figure 5: Select A Target Type

Figure 6 shows the addition of a target named "Build PPC".

Name The New Target

Figure 6: Name The New Target

Next, add a build phase to the Build PPC target. This phase will execute a script to setup the build environment, and create the PowerPC libraries. Right-click on "Build PPC", and select Add > New Build Phase > New Run Script Build Phase.

Add A Run Script Build Phase To The Target

Figure 7: Add A Run Script Build Phase To The Target

If you double-click on Targets > Build PPC > Run Script, Xcode will display a dialog where you can enter the script shown in Figure 8 (see Listings 2 and 3 under Build Scripts below for the actual code).

The Build PPC Run Script Dialog

Figure 8: The Build PPC Run Script Dialog

The project needs several additional targets, for building the i386 libraries, the Universal libraries, and the test applications. See Figure 9. Each of these targets is of type "Aggregate". An aggregate target has dependencies on other targets. The order in which these dependencies are listed determines the build sequence. Our OpenSSL project uses aggregate targets to divide the build process into discrete, ordered steps.

All Targets

Figure 9: All Targets

The Build Universal target includes dependencies on both the Build PPC and Build i386 targets. If you double-click on Build Universal, Xcode will display the target info dialog. Click the "+" icon at the bottom to add a dependency. You can also drag a target to another to add it as a dependency. See Figure 10.

All Targets

Figure 10: Dependencies In The Build Universal Target

Adding the Build Scripts

Listings 2 and 3 show the scripts for building the PowerPC and i386 libraries. These scripts are included in the project download that accompanies this article. Each script configures the build environment to match the desired architecture, builds the libraries, and moves the libraries into the architecture-specific build folder.

Listing 2: The Build PPC Script

# shell script goes here
BUILD_ARCH=build/ppc
./config
make build_libs
mkdir -p $BUILD_ARCH
mv *.a $BUILD_ARCH
echo "Done"
exit 0

Listing 3: The Build i386 Script

# shell script goes here
ARCH_DIR=build/i386
make clean
./Configure 386 darwin-i386-cc
make build_libs "CC=cc -arch i386"
mkdir -p $ARCH_DIR
ls *.a > libnames.tmp
mv *.a $ARCH_DIR
exit 0

There are some minor differences between these scripts. For example, Build PPC does not run make clean because it is the first target in the sequence, so there are no build products to clean up. However, if you need to run the targets multiple times for any reason, you may find it convenient to add make clean to the Build PPC script.

Build i386 also writes the names of the output files to a file, libnames.tmp. This file is used in the Run lipo script when creating the Universal Binaries.

Using lipo to Create the Universal Binaries

The lipo tool is used to manipulate Universal Binaries. We use lipo to combine the PowerPC and Intel forks from each library (crypto and ssl), resulting in a Universal Binary for each. In Listing 4, Run lipo iterates over each library name, in each folder under build (build/ppc and build/i386), and combines the separate architectures into a Universal Binary for each of crypto and ssl.

Listing 4: The Run lipo Script

# shell script goes here
for lib in `cat libnames.tmp`; do
 lipo -create build/*/$lib -output $lib
done
exit 0

You can check the libraries after running this script. The file command prints architecture information about a file. So does lipo -info. Listing 5 shows these commands in action.

Listing 5: Get Info Using the file and lipo Commands

Mertz:/Volumes/Tiger/Users/asd/openssl-0.9.8a asd$ file *
libcrypto.a: Mach-O fat file with 2 architectures
libcrypto.a (for architecture ppc):     current ar archive
libcrypto.a (for architecture i386):    current ar archive random library

libssl.a:    Mach-O fat file with 2 architectures
libssl.a (for architecture ppc):        current ar archive
libssl.a (for architecture i386):       current ar archive random library

Mertz:/Volumes/Tiger/Users/asd/openssl-0.9.8a asd$ lipo -info *
Architectures in the fat file: libcrypto.a are: ppc i386 
Architectures in the fat file: libssl.a are: ppc i386 

Now, how do we know the libraries work? To answer that question we need to build some test apps.

Testing The Libraries

OpenSSL includes a number of test files, usually just a main() function that exercises a specific portion of each library. Listing 5 contains the script for building the test applications. The first command, ranlib, updates the symbols listed in the table of contents of the libcrypto and libssl libraries. This is needed when you copy a static library to a machine with a different architecture, or even to a different folder on the same architecture. In these scenarios, you must rebuild the symbol table in the library before you can link an application against it. Otherwise you will get a linker error about the library being out-of-date. See man ranlib for more information.

Listing 5: The Build Tests Script

ranlib *.a
./config
make build_tests

Listing 6 shows the result of running the test named castTest. Life is good.

Listing 6: Running the Cast Test Application

Mertz:/Volumes/Tiger/Users/asd/OpenSSL/test/castTest; exit
In main() checkpoint 0
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
ecb cast5 ok
  In main() checkpoint 2
This test will take some time....123456789ABCDEF ok

The test applications provide an easy path to getting familiar with the libraries and how to use them, in preparation for incorporating OpenSSL into your own application.

You may need to change the values of LIBCRYPTO and LIBSSL in /test/Makefile as shown in Listing 7. If you do not, you may get linker errors regarding undefined symbols during the building of the test apps. If you copy the entire project from one machine to the other, then you only need to perform this step once. Otherwise, make sure you apply this change on both machines.

Listing 7: Change References To The Libraries in /test/Makefile

# Change these lines:

LIBCRYPTO= -L.. -lcrypto
LIBSSL= -L.. -lssl

# to

LIBCRYPTO= ../libcrypto.a
LIBSSL= ../liblssl.a

Testing On Intel

If you have access to an Intel-based Mac, perhaps a Mac mini with a KVM (keyboard/video/monitor) switch, you can copy the entire project, including the built libraries, over for this part of the discussion. Otherwise, you may have to get creative, perhaps by convincing a friend or colleague to assist you with the testing. This is certainly in the spirit of the open source community!

Figure 11 shows the "About This Mac" box from an Intel-based Mac that will serve as a test platform.

About This Intel-Based Mac

Figure 11: About This Intel-Based Mac

On the Intel-based Mac, we build and run the same cast test application, except this time on the Intel-based Mac. The output in Listing 8 looks the same as when we ran it on PowerPC. Excellent!

Listing 8: Running the Cast Test on Intel

Twenty-Something:~ asd$ /Users/asd/OpenSSL/test/castTest; exit
In main() checkpoint 0
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
In CAST_decrypt()
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
In CAST_decrypt()
In main() checkpoint 0.5
  In main() checkpoint 1
  In main() checkpoint 2
  In main() checkpoint 4
In CAST_decrypt()
ecb cast5 ok
  In main() checkpoint 2
This test will take some time....123456789ABCDEF ok

For More Information

Using the techniques discussed in this article, you can migrate other open source projects to the Universal Binary architecture. The sources below will help you find more specific information.

Posted: 2006-4-24