IOS (Command Line)

From Crypto++ Wiki
Jump to navigation Jump to search

This page will provide instructions for cross-compiling Crypto++ on the command line for iOS. There are four steps to building Crypto++ for iOS, and the process will create an iOS version of cryptest.exe, a dynamic library, and a static library. You will only use the static library (libcryptopp.a) in your Xcode projects, unless you have a jailbroken device. If you have a jailbroken device, then you can use scp (or other program) to transfer cryptest.exe and the test vectors to the device. Once on the device, you can then execute the tests from a mobile terminal.

Cross-compiling for iOS requires Xcode, so you should ensure its installed before you begin. The instructions below are for Xcode 5.1 and above, which means Xcode will be located at /Applications/Xcode.app. You will have to modify the setenv-ios.sh if you want to use Xcode 3 and earlier (which is located in /Developer/Xcode.app).

The examples below demonstrate a device build. For example, you will see -arch armv7 and a path that includes /Applications/Xcode.app/.../Platforms/iPhoneOS.platform. If you are building for the simulator, you should see -arch i386 and a path that includes /Applications/Xcode.app/.../Platforms/iPhoneSimulator.platform. The changes are made via setenv-ios.sh discussed below.

Note well: The script to build the library from the command line is called setenv-ios.sh, and it was added to the library sources in March 2016. They are available in Git and library ZIP files after 5.6.3. Also see Commit a78b9dfa0840f92f, Added iOS environment and test script.

Note well: Crypto++ 5.6.4 and later added code to take advantage of NEON, CRC and Crypto extensions. You should use Crypto++ 5.6.4 or later if possible. You should also avoid -DCRYPTOPP_DISABLE_ASM for Crypto++ 5.6.4 or later. If you experience missing symbols for AlignedAllocate and AlignedDeallocate, then be sure you are using Crypto++ 5.6.4 or later. Also see Missing AlignedAllocate/AlignedDeallocate iOS/ARM64 on the mailing list.

A wiki page is available for compiling Crypto++ under Xcode at iOS (Xcode). Another Apple wiki page is available at OS X (Command Line).

Set the Environment

Cryptopp-ios-100.png

Before you begin you must set the cross-compilation environment. Setting the environment will do two things. First, it will ensure the iOS toolchain is on-path. Second, it will ensure some environmental variables are set so the makefile does not erroneously configure itself for the host (i.e., Linux or Mac OS X) instead of the target (i.e., iPad or iPhone). For example, the script will set IS_IOS and IS_CROSS_COMPILE to ensure IS_X86 and IS_DARWIN are set to 0 in the makefile.

Run setenv-ios.sh to set the environment. setenv-ios.sh needs two environmental variables set. The first is the IOS_SDK. The second is IOS_CPU. The source command is required to make changes to the current shell.

If you are configuring for a device build, then you should see output similar to below:

$ IOS_SDK=iPhoneOS IOS_CPU=arm64 source ./setenv-ios.sh 
Configuring for iPhoneOS (arm64)
XCODE_TOOLCHAIN: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/
IOS_SDK: iPhoneOS
IOS_CPU: arm64
IOS_SYSROOT: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk
IOS_CXXFLAGS: -arch arm64 -miphoneos-version-min=6

If you are building for the simulator, then run the script with the simulator argument:

$ IOS_SDK=iPhoneSimulator IOS_CPU=i386 source ./setenv-ios.sh 
Configuring for iPhoneSimulator (i386)
XCODE_TOOLCHAIN: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/
IOS_SDK: iPhoneSimulator
IOS_CPU: i386
IOS_SYSROOT: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.2.sdk
IOS_CXXFLAGS: -arch i386 -miphoneos-version-min=6

The different IOS_SDK values are:

  • iPhoneOS
  • iPhoneSimulator
  • AppleTVOS
  • AppleTVSimulator
  • WatchOS
  • WatchSimulator

The different IOS_CPU values are:

  • armv7
  • arm64
  • i386
  • x86_64

To verify the proper IOS_* and XCODE_* variables have been exported, execute printenv:

$ printenv| grep -E 'IOS|XCODE' | sort
IOS_CXXFLAGS=-arch i386 -miphoneos-version-min=6 -DCRYPTOPP_DISABLE_ASM
IOS_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.2.sdk
IS_IOS=1

To verify the path has been set, simply echo it from the command line (PATH used IOS_TOOLCHAIN and XCODE_TOOLCHAIN):

$ echo $PATH
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/u
sr/bin/:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/opt/local/
sbin/:/opt/local/bin

Build the Library

Cryptopp-ios-101.png

To build the library, execute make static dylib cryptest.exe. If the environment is set and the makefile is patched, then you will see output similar to below. Note well: you must use GNUmakefile-cross to build the library. The makefile picks up variables set-up by setenv-ios.sh.

$ make -f GNUmakefile-cross 
Here's what we found... IS_X86: 0, IS_X64: 0, IS_ARM32: 0, IS_ARMV8: 1

clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -arch arm64 -miphoneos-version-min=6 -stdlib=libc++ --sysroot
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk" -c cryptlib.cpp
clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -arch arm64 -miphoneos-version-min=6 -stdlib=libc++ --sysroot
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk" -c cpu.cpp
clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -arch arm64 -miphoneos-version-min=6 -stdlib=libc++ --sysroot
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.2.sdk" -c integer.cpp
...

You can verify the library was built for the correct architecture with the following.

$ xcrun -sdk iphoneos lipo -info libcryptopp.a cryptest.exe
input file libcryptopp.a is not a fat file
Non-fat file: libcryptopp.a is architecture: arm64
Non-fat file: cryptest.exe is architecture: arm64

If you want to reduce the final executable size of a program when linking against the static library, then use the lean makefile target. It will add -ffunction-sections -fdata-sections.

$ make -f GNUmakefile-cross lean
clang++ -DNDEBUG -g2 -Os -fPIC -pipe -Wall -miphoneos-version-min=7 -arch arm64 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk
-stdlib=libc++ -ffunction-sections -fdata-sections -c cryptlib.cpp
clang++ -DNDEBUG -g2 -Os -fPIC -pipe -Wall -miphoneos-version-min=7 -arch arm64 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk
-stdlib=libc++ -ffunction-sections -fdata-sections -c cpu.cpp
clang++ -DNDEBUG -g2 -Os -fPIC -pipe -Wall -miphoneos-version-min=7 -arch arm64 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk
-stdlib=libc++ -ffunction-sections -fdata-sections -c integer.cpp
...

You should perform a final link with -Wl,-dead_strip. The Crypto++ makefile does it for cryptest.exe, but you will have to do it for your program:

clang++ -o cryptest.exe -DNDEBUG -g2 -Os -fPIC -pipe -Wall -miphoneos-version-min=7 -arch 
arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Dev
eloper/SDKs/iPhoneOS7.1.sdk -stdlib=libc++ -ffunction-sections -fdata-sections adhoc.o tes
t.o bench1.o bench2.o validat0.o validat1.o validat2.o validat3.o datatest.o regtest.o fip
salgt.o dlltest.o ./libcryptopp.a -Wl,-dead_strip

Execute the Program

If you have a jailbroken device then you can move the cryptest.exe executable and test vectors to the device to test the library. The procedures in this section use a jailbroken iPad 1 with an A4 processor running iOS 5.1.1. Jailbreaking is well beyond this wiki page, but useful information can be found at Jailbreak QA run by Saurik.

Before transferring cryptest.exe to the device, you must sign the program or use ldid. ldid is spotty at times, so its usually easier to sign the binary.

$ codesign -s "Johnny Developer" cryptest.exe

If you experience a failure due to ambiguous identities (matches both "Mac Developer" and "iPhone Developer"), then use:

$ codesign -s "iPhone Developer: Johnny Developer" cryptest.exe

Once the program is signed, transfer cryptest.exe, TestVectors and TestData to the device. Below, Fugu (a SFTP, SCP and SSH client) shows the local file system (MacBook Pro) on the left, and the remote file system (iPad 1) on the right.

Cryptopp-ios-103.png

With the program and test vectors on the device, SSH into the deivce and exectue the test program.

Cryptopp-ios-105.png
riemann::cryptopp$ ssh mobile@192.168.1.13
mobile@192.168.1.13's password: 
iPad:~ mobile$ cd cryptopp/
iPad:~/cryptopp mobile$ ls
TestData/  TestVectors/  cryptest.exe*
iPad:~/cryptopp mobile$ ./cryptest.exe v
Using seed: 1374910436

Testing Settings...

passed:  Your machine is little endian.
passed:  CRYPTOPP_ALLOW_UNALIGNED_DATA_ACCESS is not defined. Will restrict to 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
...

If all goes well, the tail of cryptest.exe v will display "All tests passed!" as shown below.

Cryptopp-ios-106.png

Install the Library

Once the library has been verified on a device, copy the header files (*.h), the dynamic library (libcryptopp.dylib), and the static library (libcryptopp.a) to a directory for use in other projects.

Or, you can run sudo make -f GNUmakefile-cross install PREFIX=/usr/local/ios-armv7 since the libraries are for the ARMv7 platform.

Xcode Project

This section provides quick and dirty instructions for using the library in iPhone, iPad, and Simulator projects. Also see iOS (Xcode) on the Crypto++ wiki.

First, combine all your libcryptopp.a files into a single, Mach-O (fat) binary. The armv7/libcryptopp.a (and others below) assumes you saved the library between builds among architectures.

$ lipo create \
    armv7/libcryptopp.a \
    armv7s/libcryptopp.a \
    arm64/libcryptopp.a \
    i386/libcryptopp.a \
    x86_64/libcryptopp.a \
    -output libcryptopp.a

Second, verify all the architectures are present:

$ xcrun -sdk iphoneos lipo -info libcryptopp.a
Architectures in the fat file: libcryptopp.a are: armv7 armv7s arm64 i386 x86_64

Third, remove non-archive files. Even though you add libcryptopp.a to the project, Xcode will still try and link against the shared objects. Apparently, Apple developers and employees did not get the memo about static linking only:

$ rm *.so *.dylib *.exe

Fourth, install the library:

sudo make -f GNUmakefile-cross install PREFIX=/usr/local/cryptopp-ios

If you are installing in your $HOME directory, then you can omit sudo.

Fifth, fix the permissions on the files as required:

$ sudo chmod -R a+r /usr/local/cryptopp-ios/*
$ sudo chmod -R a+x /usr/local/cryptopp-ios/lib/*

If you are installing in your $HOME directory, then you can omit this step.

Sixth, create an Xcode project. Add /usr/local/cryptopp-ios/include as a user header search path. Also add /usr/local/cryptopp-ios/lib as a library path. If you fetched a prebuilt library from Github, then change the path accordingly.

Xcode-cryptopp-2000.png

Under this path scheme, you will include files as:

#include "cryptopp/cryptlib.h"

Seventh, add libcryptopp.a to the project per the instructions at [hcodep://stackoverflow.com/questions/3352664/how-to-add-existing-frameworks-in-xcode-4 How to “add existing frameworks” in Xcode 4?].

Xcode-cryptopp-2001.png

Ensure the following Build Secodeings are present as shown in the image below. The library is built to use LLVM's libc++ because it is Xcode's default. The library does not use GNU's libstdc++ by default.

  • C++ Standard Library = libc++ (not libstdc++)
  • Compile Source As = Objective-C++ (invokes clang++)
Xcode-cryptopp-1999.png

If the above are not set, you will receive errors about missing symbols even though it appears the symbols are present. For example, you will receive an error that std::__1::string, std::__1::vector, std::__1::char_traits<char>, or std::__1::allocator<char> cannot be found.

If you really need GNU's libstdc++ (due to other libraries), then you will need to rebuild the Crypto++ library after changing CXXFLAGS in the GNUmakefile and Xcode secodeings.

Finally, use Crypto++ as usual.

Xcode-cryptopp-2002.png

ARM64 Simulator

With the introduction of ARM64, it appears Apple utilizes x86_64 for some simulators with some versions of Xcode.

The setenv-ios.sh script will respond to ./setenv-ios.sh x86_64 by selecting the iPhoneSimulator.platform, and use -arch x86_64 as a compiler flag to clang++. The SDKs distributed with Xcode 5 and 6 appear to honor the setting.

However, Xcode 5 will use i386 when building for iPad Retina (64-bit) under the simulator. If you attempt to use -arch x86_64 for the Xcode 5/iOS 7 simulator, then the linker will fail with the error:

ld: building for MacOSX, but linking against dylib built for iOS Simulator file '/Applications/Xcode.app/Contents/
Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/lib/libSystem.dylib' for 
architecture x86_64

Xcode 5/iOS 7 can be made to work for the iOS Simulator by including -miphoneos-version-min=8.

Xcode 6 will work as expected with iPhoneSimulator.platform and -arch x86_64.

Preprocessor Macros

Apple/Xcode offers three defines of interest for those who are mindful of cross platform source code. __APPLE__ will defined on the platform, and __OBJC__ will be defined when Objective C is available. When using a BSD based kernel, __MACH__ will also be defined.

Apple provides some guidance in preprocessor macros in Conditionally Compiling for Different SDKs. A number of predefined macros are available from Availability.h and TargetConditionals.h. The former is more fine grained and allows you to differentiate between OS X and iOS (but not Simulator) and version numbers of each. The later is coarse grained and allows you to differentiate between OS X, iOS, Simulator and CPU types.

TargetConditionals.h is used prodigiously in samples available on the Apple website. There is a TargetConditionals.h in the base system's /usr/include, and in each of the SDKs located at /Applications/Xcode.app/.../<platform>/.../usr/include. The trick to using them correctly is treating them as a compiler Boolean, and not using them as defined conditionals like #if defined(TARGET_OS_IPHONE).

According to comments in the header files regarding TARGET_OS_*:

    These conditionals specify in which Operating System the generated code will
    run. The MAC/WIN32/UNIX conditionals are mutually exclusive.  The EMBEDDED/IPHONE 
	conditionals are variants of TARGET_OS_MAC. 

        TARGET_OS_MAC           - Generate code will run under Mac OS
        TARGET_OS_WIN32         - Generate code will run under 32-bit Windows
        TARGET_OS_UNIX          - Generate code will run under some non Mac OS X unix 
        TARGET_OS_EMBEDDED      - Generate code will run under an embedded OS variant
                                  of TARGET_OS_MAC
        TARGET_OS_IPHONE        - Generate code will run under iPhone OS which 
                                  is a variant of TARGET_OS_MAC.
    TARGET_IPHONE_SIMULATOR     - Generate code for running under iPhone Simulator

Here is how the various Apple defines are translated into CRYPTOPP_IPHONE CRYPTOPP_MAC_OSX and CRYPTOPP_IPHONE_SIMULATOR for targeting Apple platforms during cross compiles:

#if defined(__APPLE__)
# include "TargetConditionals.h"

# if defined(TARGET_IPHONE_SIMULATOR) && (TARGET_IPHONE_SIMULATOR != 0)
#  define CRYPTOPP_APPLE_SIMULATOR 1
# elif defined(TARGET_APPLE_IPHONE) && (TARGET_OS_IPHONE != 0)
#  define CRYPTOPP_IPHONE 1
# elif defined(TARGET_OS_WATCH) && (TARGET_OS_WATCH != 0)
#  define CRYPTOPP_APPLE_WATCH 1
# elif defined(TARGET_OS_MAC) && (TARGET_OS_MAC != 0)
#  define CRYPTOPP_APPLE_OSX 1
# else
#  error Unknown Apple platform
# endif

#endif /* Apple */

cdefs.h includes a few interesting symbols: PRODUCT_AppleTV, PRODUCT_iPhone and PRODUCT_MacOSX. Unfortunately, PRODUCT_iPhone never seems to be defined, and cdefs.h should not be relied upon.

If all you need is to differentiate between OS X and iOS, then the following from Availability.h should work fine:

#if defined(__APPLE__)
# include "Availability.h"

# if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
#  define CRYPTOPP_MAC_OSX 1
# elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#  define CRYPTOPP_IPHONE 1
# else
#  error Unknown Apple platform
# endif

#endif /* Apple */

Base System

The OS X base system uses path /usr/include. The defines of interest from TargetConditionals.h are:

    #define TARGET_OS_MAC               1
    #define TARGET_OS_WIN32             0
    #define TARGET_OS_UNIX              0
    #define TARGET_OS_EMBEDDED          0 
    #define TARGET_OS_IPHONE            0 
    #define TARGET_IPHONE_SIMULATOR     0 

iPhoneSimulator

The iPhone Simulator's SDK uses /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/usr/include. The defines of interest from TargetConditionals.h are:

    #define TARGET_OS_MAC               1
    #define TARGET_OS_WIN32             0
    #define TARGET_OS_UNIX              0
    #define TARGET_OS_EMBEDDED          0 
    #define TARGET_OS_IPHONE            1 
    #define TARGET_IPHONE_SIMULATOR     1 

iPhone (and iPad)

The iPhone's SDK uses /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk/usr/include. The defines of interest from TargetConditionals.h are:

    #define TARGET_OS_MAC               1
    #define TARGET_OS_WIN32             0
    #define TARGET_OS_UNIX              0
    #define TARGET_OS_EMBEDDED          1 
    #define TARGET_OS_IPHONE            1 
    #define TARGET_IPHONE_SIMULATOR     0 

Mac OS X

The OS X SDK uses /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/usr/include. The defines of interest from TargetConditionals.h are:

    #define TARGET_OS_MAC               1
    #define TARGET_OS_WIN32             0
    #define TARGET_OS_UNIX              0
    #define TARGET_OS_EMBEDDED          0 
    #define TARGET_OS_IPHONE            0 
    #define TARGET_IPHONE_SIMULATOR     0

CPU Architectures

In addition to providing platforms, TargetConditionals.h also provides CPU architectures by way of TARGET_CPU_*:

    These conditionals specify which microprocessor instruction set is being
    generated.  At most one of these is true, the rest are false.

        TARGET_CPU_PPC          - Compiler is generating PowerPC instructions for 32-bit mode
        TARGET_CPU_PPC64        - Compiler is generating PowerPC instructions for 64-bit mode
        TARGET_CPU_68K          - Compiler is generating 680x0 instructions
        TARGET_CPU_X86          - Compiler is generating x86 instructions
        TARGET_CPU_X86_64       - Compiler is generating x86_64 instructions
        TARGET_CPU_ARM          - Compiler is generating ARM instructions
        TARGET_CPU_ARM64        - Compiler is generating ARM64 instructions
        TARGET_CPU_MIPS         - Compiler is generating MIPS instructions
        TARGET_CPU_SPARC        - Compiler is generating Sparc instructions
        TARGET_CPU_ALPHA        - Compiler is generating Dec Alpha instructions

Downloads

None. The files are present in GitHub.