iosxcodelinkertvosmach-o

How to edit load command in Mach-O file and successfully link it on tvOS?


Let's assume that I have a .a fat library which is built for several architectures (e.g. armv7, armv7s, i386, x86_64, arm64). Library is built for iOS (has cmd LC_VERSION_MIN_IPHONEOS and version 6.0 in it). For example when I'm running $ otool -lv I get sections with such load command:

Load command 1
      cmd LC_VERSION_MIN_IPHONEOS
  cmdsize 16
  version 6.0
      sdk n/a

And I'd like to replace it to:

Load command 1
      cmd LC_VERSION_MIN_TVOS
  cmdsize 16
  version 9.0
      sdk n/a

Does anybody know how can I do this or can point out to some helpful resources? Also will I be able to link modified library on tvOS?


Solution

  • If you have access to the source, the best solution would obviously be to recompile it for tvOS.

    If you do not have access to the source, the quick and dirty solution would be to open the library in a hex editor, search for these bytes (once per object file):

    25000000 10000000 00000600 00000000
    

    And replace them with these:

    2f000000 10000000 00000900 00000000
    

    A more stable solution would be to write a small C program to parse and edit the header of your object files. On a Mac with Xcode installed, all information you need for that (along with helpful comments) are available from the <mach-o/loader.h> header file. For everyone else, that file is also available from the XNU source code.

    The basic idea of iterating over all load commands looks like this, given a char buffer file containing a Mach-O:

    struct mach_header_64 *hdr = (struct mach_header_64*)file;
    for(struct load_command *cmd = (struct load_command*)(hdr + 1),
                            *end = (struct load_command*)((uintptr_t)cmd + hdr->sizeofcmds);
        cmd < end;
        cmd = (struct load_command*)((uintptr_t)cmd + cmd->cmdsize)
    )
    {
        // ...
    }
    

    Now for your purposes, you'll want to iterate until you find a load command with cmd->cmd == LC_VERSION_MIN_IPHONEOS, and replace that with LC_VERSION_MIN_TVOS. You'll also want to cast that load command to a struct version_min_command* then, which is defined as follows:

    struct version_min_command {
        uint32_t cmd;
        uint32_t cmdsize;
        uint32_t version;   /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
        uint32_t sdk;       /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
    };
    

    So in order to get a version number of 9.0, you'd assign version a value of (9 << 16).

    Then save the file back and you're done.