Matt Galloway

My home on the 'net.

Hacking up an armv7s library

NOTE: Please take care with this. I obviously cannot test if this will actually work on a new iPhone 5 device! I provide no warranty if you submit having used this and it doesn’t actually work on the new device. Please think twice before submitting an app which you have used this method to create. You don’t have to submit an armv7s binary. Just set your “Architectures” build setting to armv7 only and submit the resulting binary.

UPDATE: It worked! I tested an app that I’d used this method to build an armv7s slice with. It ran fine on my iPhone 5 :-D.

Well the iPhone 5 has been announced and it just so happens that the architecture it uses is what they’re calling armv7s. This brings in yet another architecture to the mix alongside armv6 and armv7. And I bet you are wondering why you’re getting linker errors when building for armv7s when using external libraries. It’s because those external libraries do not have armv7s versions!

If you run file on the library then you’ll see that there is no armv7s version. For example:

1
2
3
4
5
$ file libUAirship-1.2.1.a
libUAirship-1.2.1.a: Mach-O universal binary with 3 architectures
libUAirship-1.2.1.a (for architecture armv7):    current ar archive random library
libUAirship-1.2.1.a (for architecture armv6):    current ar archive random library
libUAirship-1.2.1.a (for architecture i386): current ar archive random library

So what can you do? You could wait for the library to be updated, or you could just follow these steps…

So what’s the deal?

Well, the problem is that when the linker does its merry business linking together all your object files, it is told what architecture to link for. Each of your libraries’ .a files will most likely be what are called “fat” meaning they have more than one architecture in them. But the linker won’t be able to find the armv7s version since it doesn’t exist in there.

But, we know that armv7s is a superset of armv7 (it’s just got a new version of the floating point unit so only adds new instructions). So what we can do is to copy the armv7 part of the library and add it again but tell it that it’s for armv7s. That sounds simple, but there’s more to it than that.

Inside each architecture’s portion of the fat library is something called an object file archive. This contains a collection of .o files that were combined together to form the library. Inside each .o is the code for each method. The linker uses these to build the final app binary, picking all the methods it needs to create the app. The problem is that these .o files also have a header to say what architecture they’re for.

Inside this header (called a Mach-O header) is a field for the CPU type and the CPU subtype. ARM is CPU type 12, armv7 is CPU subtype 9 and armv7s is CPU subtype 11. So, all we need to do is toggle all the 9s to 11s, right? Yup! But that’s easier said than done.

My solution is a script that strips out the armv7 portion of the fat library and then unpacks the archive into its constituent .o files. Then I wrote a little C program to do the 9 => 11 toggling which is run on each of the .o files. Then finally the new .o files are packaged up into a new portion which is re-added to the fat library.

Simple!

So, if you’re ready to get going then read on…

Do you need this to submit an app?

No.

Do not use this unless you really understand what you’re doing. You do not need to submit with an armv7s binary. Just set your Architectures build setting to armv7 only and submit the resulting binary.

A program you’ll need

The first thing you’ll need is the following program written in C:

armv7sconvert.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
 * armv7sconvert <infile> <outfile>
 * Switches CPU subsystem type to armv7s
 *
 * By Matt Galloway - http://www.galloway.me.uk
 * 
 * Based on g3spot.c from http://redbutton.sourceforge.net (c) Simon Kilvington, 2009
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <mach-o/loader.h>

void fatal(char *msg, ...);
void fatal(char *msg, ...) {
  va_list ap;

  va_start(ap, msg);
  vfprintf(stderr, msg, ap);
  fprintf(stderr, "\n");
  va_end(ap);

  exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
  if (argc != 3) {
      fatal("Syntax: %s <in_file> <out_file>", argv[0]);
  }

  char *inName = argv[1];
  char *outName = argv[2];

    FILE *inFile = fopen(inName, "r");
  if (inFile == NULL) {
      fatal("Unable to read %s: %s", inName, strerror(errno));
  }

  /* find out how big it is */
  fseek(inFile, 0, SEEK_END);
  long size = ftell(inFile);
  rewind(inFile);

  printf("%s: %lu bytes\n", inName, size);

  /* read it all into memory */
    unsigned char *buf = malloc(size);
  if (buf == NULL) {
      fatal("Out of memory");
  }
  if (fread(buf, 1, size, inFile) != size) {
      fatal("Error trying to read %s: %s", inName, strerror(errno));
  }
  if (fclose(inFile)) {
      fatal("Error trying to close %s: %s", inName, strerror(errno));
  }

  struct mach_header *mach_hdr = (struct mach_header *) (buf);
  printf("Mach magic: 0x%08x\n", mach_hdr->magic);
  if (mach_hdr->magic != MH_MAGIC) {
      fatal("Wrong magic number (expecting 0x%08x)", MH_MAGIC);
  }
  printf("CPU type: %d\n", ntohl(mach_hdr->cputype));
  printf("CPU sub-type: %d\n", ntohl(mach_hdr->cpusubtype));
  printf("*** Changing to CPU sub-type 11\n");
  mach_hdr->cpusubtype = 11;

  printf("Saving as %s\n", outName);

    FILE *outFile = fopen(outName, "w");
  if (outFile == NULL) {
      fatal("Unable to write %s: %s", outName, strerror(errno));
  }
  if (fwrite(buf, 1, size, outFile) != size) {
      fatal("Error trying to write %s: %s", outName, strerror(errno));
  }
  if (fclose(outFile)) {
      fatal("Error trying to close %s: %s", outName, strerror(errno));
  }

  return EXIT_SUCCESS;
}

Copy it, and save it as armv7sconvert.c. Then compile it with:

1
clang -o armv7sconvert armv7sconvert.c

Then add this to ~/bin and add ~/bin to your path by editing ~/.profile and adding:

~/.profile
1
PATH=${PATH}:${HOME}/bin

A script!

Now you’ll want the script which does the hard work of unpacking the library, running the armv7sconvert over the object files and repacking it. Copy and paste the following into a file called armv7sconvert.sh also in ~/bin:

armv7sconvert.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

CONVERT="armv7sconvert"
ARCHIVE="$1"
LIPO="xcrun -sdk iphoneos lipo"
AR="xcrun -sdk iphoneos ar"
TEMP="`mktemp -d -t build`"

cp ${ARCHIVE} ${TEMP}
pushd ${TEMP}
${LIPO} -thin armv7 ${ARCHIVE} -output ${ARCHIVE}.armv7
${AR} -x ${ARCHIVE}.armv7
find . -name '*.o' -exec ${CONVERT} {} {}2 \;
rm *.o
${AR} -r ${ARCHIVE}.armv7s *.o2
rm *.o2
${LIPO} -create -arch armv7s ${ARCHIVE}.armv7s ${ARCHIVE} -output ${ARCHIVE}.new
popd

mv ${TEMP}/${ARCHIVE}.new ${ARCHIVE}

rm -rf ${TEMP}

What this does is a bit magical. I’ll explain later when I get chance. But now put this in your path somewhere and call it armv7sconvert.sh. Then run this command on it:

1
chmod +x armv7sconvert.sh

Convert those libraries!

Now go to where your library is located and do this:

1
armv7sconvert.sh libUAirship-1.2.1.a

That should now have the armv7s portion added to it. To confirm, do:

1
2
3
4
5
6
$ file libUAirship-1.2.1.a
libUAirship-1.2.1.a: Mach-O universal binary with 4 architectures
libUAirship-1.2.1.a (for architecture cputype (12) cpusubtype (11)):    current ar archive random library
libUAirship-1.2.1.a (for architecture armv7):    current ar archive random library
libUAirship-1.2.1.a (for architecture armv6):    current ar archive random library
libUAirship-1.2.1.a (for architecture i386): current ar archive random library

You should then see armv7 and armv7s (or it might just say CPU type 12 and CPU sub-type 11 — just another name for armv7s).

And the verdict is…

I had an app that I’d done this little hack on for 4 libraries it used. I created just an armv7s binary and had it ready and waiting for my iPhone 5 to test on. I tested it and it worked like a dream. No problems what-so-ever. So, I would say that it’s a success!

Comments