Info.plist for command line tools

In the past if you wanted to include data in your command line app, you could either include it in a separate file or do some linker trickery to embed it. Personally, I never really got into the linker trickery (though it’s probably the better solution of the two).

For a while, though, Xcode has supported a build option Create Info.plist Section In Binary, also known as CREATE_INFOPLIST_SECTION_IN_BINARY. If on, this will embed the target’s Info.plist into the executable as a data segment.

I couldn’t find any documentation on how to get it out, though. After a lot of searching, I came up with getsectbyname. This is defined in mach-o/getsect.h like so:

extern const struct section_64 *getsectbyname(
    const char *segname,
    const char *sectname);

(There’s a 32-bit version as well, but I was only interested in 64-bit.)

One of the fields in section_64 was the address of the data in memory, another its size. Easy to wrap with a NSData, right?

Not so fast. This worked in debug and release builds when run in Xcode, but didn’t work when run on Terminal or Instruments. There, accessing the raw bytes caused a segmentation fault.

A tweet from Greg Parker put me on track.

The difference turned out to be Address space layout randomization (ASLR). You can read all about it on Wikipedia, but the gist of it is that in order to make it harder to exploit a vulnerability many operating systems have implemented a system where executables are randomly located in the address space. This makes it harder for injected code to access application or system code. The results returned by getsectbyname don’t take ASLR into account.

Instead of using getsectbyname, I had to use getsectiondatagetsectiondata would return a pointer to the exact data I needed. It took me quite a while to find anything relevant online.

It’s declared like this:

extern uint8_t *getsectiondata(
    const struct mach_header *mhp,
    const char *segname,
    const char *sectname,
    unsigned long *size);

But what’s that fist thing, the mach_header?

It took me a long time to find anything relevant, but I eventually found out that I needed to use _mh_execute_header from mach-o/ldsyms.h.

The resulting code looks like this, and works:

#include <mach-o/getsect.h>
#include <mach-o/ldsyms.h>

- (instancetype)init {
    self = [super init];
    if (!self) return nil;
    
    NSError *e;
    unsigned long size;
    void *ptr = getsectiondata(&_mh_execute_header, "__TEXT", "__info_plist", &size);
    NSData *plistData = [NSData dataWithBytesNoCopy:ptr length:size freeWhenDone:NO];
   _infoPlistContents = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:NULL error:&e];

   return self;
}

This only works in the application; if you need to do this from a dynamic library, try this approach from Cédric Luthi.

Full credit to those two, but I wanted to post something about this so that future searches might find it. Thanks, guys!