How to get the build UUID in runtime and the image base address

3 Solutions Collect From Internet About “How to get the build UUID in runtime and the image base address”

Here is a solution similar to Kerni’s answer but which works for any platform (iOS device + simulator and OS X) and any architecture (32-bit + 64-bit). It also returns a NSUUID instead of a NSString.

#import <mach-o/dyld.h>
#import <mach-o/loader.h>

static NSUUID *ExecutableUUID(void)
{
    const struct mach_header *executableHeader = NULL;
    for (uint32_t i = 0; i < _dyld_image_count(); i++)
    {
        const struct mach_header *header = _dyld_get_image_header(i);
        if (header->filetype == MH_EXECUTE)
        {
            executableHeader = header;
            break;
        }
    }

    if (!executableHeader)
        return nil;

    BOOL is64bit = executableHeader->magic == MH_MAGIC_64 || executableHeader->magic == MH_CIGAM_64;
    uintptr_t cursor = (uintptr_t)executableHeader + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header));
    const struct segment_command *segmentCommand = NULL;
    for (uint32_t i = 0; i < executableHeader->ncmds; i++, cursor += segmentCommand->cmdsize)
    {
        segmentCommand = (struct segment_command *)cursor;
        if (segmentCommand->cmd == LC_UUID)
        {
            const struct uuid_command *uuidCommand = (const struct uuid_command *)segmentCommand;
            return [[NSUUID alloc] initWithUUIDBytes:uuidCommand->uuid];
        }
    }

    return nil;
}

Your own suggestion is way too complicated.

Check this code from HockeySDK which does the same with just a couple of lines of code: https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITHockeyBaseManager.m#L136

- (NSString *)executableUUID {
  // This now requires the testing of this feature to be done on an actual device, since it returns always empty strings on the simulator
#if !TARGET_IPHONE_SIMULATOR
  const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
  for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
    const struct load_command *load_command = (const struct load_command *)command;
    if (load_command->cmd == LC_UUID) {
      const struct uuid_command *uuid_command = (const struct uuid_command *)command;
      const uint8_t *uuid = uuid_command->uuid;
      return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
               uuid[0], uuid[1], uuid[2], uuid[3],
               uuid[4], uuid[5], uuid[6], uuid[7],
               uuid[8], uuid[9], uuid[10], uuid[11],
               uuid[12], uuid[13], uuid[14], uuid[15]]
              lowercaseString];
    } else {
      command += load_command->cmdsize;
    }
  }
#endif
  return @"";
}

This can be achieved with a class I found from some colleagues that they found in plausible labs and refactored to their needs.

Here you can get the build UUID the image base address and the application name.

    /**
 * @internal
 *
 * Async-safe binary image list element.
 */
typedef struct bin_image {
    /** The binary image's header address. */
    uintptr_t header;

    /** The binary image's name/path. */
    char *name;

    /** The previous image in the list, or NULL */
    struct bin_image *prev;

    /** The next image in the list, or NULL. */
    struct bin_image *next;
} bin_image_t;

/**
 * @internal
 *
 * Async-safe binary image list. May be used to iterate over the binary images currently
 * available in-process.
 */
typedef struct bs_image_list {
    /** The lock used by writers. No lock is required for readers. */
    OSSpinLock write_lock;

    /** The head of the list, or NULL if the list is empty. Must only be used to iterate or delete entries. */
    bin_image_t *head;

    /** The tail of the list, or NULL if the list is empty. Must only be used to append new entries. */
    bin_image_t *tail;

    /** The list reference count. No nodes will be deallocated while the count is greater than 0. If the count
     * reaches 0, all nodes in the free list will be deallocated. */
    int32_t refcount;

    /** The node free list. */
    bin_image_t *free;
} bs_image_list_t;

/**
 * @internal
 *
 * Shared dyld image list.
 */
static bs_image_list_t shared_image_list = { 0 };

/**
 * @internal
 *
 * Maintains a linked list of binary images with support for async-safe iteration. Writing may occur concurrently with
 * async-safe reading, but is not async-safe.
 *
 * Atomic compare and swap is used to ensure a consistent view of the list for readers. To simplify implementation, a
 * write mutex is held for all updates; the implementation is not designed for efficiency in the face of contention
 * between readers and writers, and it's assumed that no contention should realistically occur.
 * @{
 */

/**
 * Initialize a new binary image list and issue a memory barrier
 *
 * @param list The list structure to be initialized.
 *
 * @warning This method is not async safe.
 */
static void image_list_init (bs_image_list_t *list) {
    memset(list, 0, sizeof(*list));

    list->write_lock = OS_SPINLOCK_INIT;
}

/**
 * Free any binary image list resources.
 *
 * @warning This method is not async safe.
 */
static void image_list_free (bs_image_list_t *list) {
    bin_image_t *next = list->head;
    while (next != NULL) {
        /* Save the current pointer and fetch the next pointer. */
        bin_image_t *cur = next;
        next = cur->next;

        /* Deallocate the current item. */
        if (cur->name != NULL)
            free(cur->name);
        free(cur);
    }
}

/**
 * Append a new binary image record to @a list.
 *
 * @param list The list to which the image record should be appended.
 * @param header The image's header address.
 * @param name The image's name.
 *
 * @warning This method is not async safe.
 */
static void image_list_append (bs_image_list_t *list, uintptr_t header, const char *name) {
    /* Initialize the new entry. */
    bin_image_t *new = calloc(1, sizeof(bin_image_t));
    new->header = header;
    new->name = strdup(name);

    /* Update the image record and issue a memory barrier to ensure a consistent view. */
    OSMemoryBarrier();

    /* Lock the list from other writers. */
    OSSpinLockLock(&list->write_lock); {

        /* If this is the first entry, initialize the list. */
        if (list->tail == NULL) {

            /* Update the list tail. This need not be done atomically, as tail is never accessed by a lockless reader. */
            list->tail = new;

            /* Atomically update the list head; this will be iterated upon by lockless readers. */
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, new, (void **) (&list->head))) {
                /* Should never occur */
                NSLog(@"An async image head was set with tail == NULL despite holding lock.");
            }
        }

        /* Otherwise, append to the end of the list */
        else {
            /* Atomically slot the new record into place; this may be iterated on by a lockless reader. */
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, new, (void **) (&list->tail->next))) {
                NSLog(@"Failed to append to image list despite holding lock");
            }

            /* Update the prev and tail pointers. This is never accessed without a lock, so no additional barrier
             * is required here. */
            new->prev = list->tail;
            list->tail = new;
        }
    } OSSpinLockUnlock(&list->write_lock);
}

/**
 * Remove a binary image record from @a list.
 *
 * @param header The header address of the record to be removed. The first record matching this address will be removed.
 *
 * @warning This method is not async safe.
 */
static void image_list_remove (bs_image_list_t *list, uintptr_t header) {
    /* Lock the list from other writers. */
    OSSpinLockLock(&list->write_lock); {
        /* Find the record. */
        bin_image_t *item = list->head;
        while (item != NULL) {
            if (item->header == header)
                break;

            item = item->next;
        }

        /* If not found, nothing to do */
        if (item == NULL) {
            OSSpinLockUnlock(&list->write_lock);
            return;
        }

        /*
         * Atomically make the item unreachable by readers.
         *
         * This serves as a synchronization point -- after the CAS, the item is no longer reachable via the list.
         */
        if (item == list->head) {
            if (!OSAtomicCompareAndSwapPtrBarrier(item, item->next, (void **) &list->head)) {
                NSLog(@"Failed to remove image list head despite holding lock");
            }
        } else {
            /* There MUST be a non-NULL prev pointer, as this is not HEAD. */
            if (!OSAtomicCompareAndSwapPtrBarrier(item, item->next, (void **) &item->prev->next)) {
                NSLog(@"Failed to remove image list item despite holding lock");
            }
        }

        /* Now that the item is unreachable, update the prev/tail pointers. These are never accessed without a lock,
         * and need not be updated atomically. */
        if (item->next != NULL) {
            /* Item is not the tail (otherwise next would be NULL), so simply update the next item's prev pointer. */
            item->next->prev = item->prev;
        } else {
            /* Item is the tail (next is NULL). Simply update the tail record. */
            list->tail = item->prev;
        }

        /* If a reader is active, simply spin until inactive. */
        while (list->refcount > 0) {
        }

        if (item->name != NULL)
            free(item->name);
        free(item);
    } OSSpinLockUnlock(&list->write_lock);
}

/**
 * Retain or release the list for reading. This method is async-safe.
 *
 * This must be issued prior to attempting to iterate the list, and must called again once reads have completed.
 *
 * @param list The list to be be retained or released for reading.
 * @param enable If true, the list will be retained. If false, released.
 */
static void image_list_set_reading (bs_image_list_t *list, bool enable) {
    if (enable) {
        /* Increment and issue a barrier. Once issued, no items will be deallocated while a reference is held. */
        OSAtomicIncrement32Barrier(&list->refcount);
    } else {
        /* Increment and issue a barrier. Once issued, items may again be deallocated. */
        OSAtomicDecrement32Barrier(&list->refcount);
    }
}

/**
 * Return the next image record. This method is async-safe. If no additional images are available, will return NULL;
 *
 * @param list The list to be iterated.
 * @param current The current image record, or NULL to start iteration.
 */
static bin_image_t *image_list_next (bs_image_list_t *list, bin_image_t *current) {
    if (current != NULL)
        return current->next;

    return list->head;
}

/**
 * @internal
 * dyld image add notification callback.
 */
static void image_add_callback (const struct mach_header *mh, intptr_t vmaddr_slide) {
    Dl_info info;

    /* Look up the image info */
    if (dladdr(mh, &info) == 0) {
        NSLog(@"%s: dladdr(%p, ...) failed", __FUNCTION__, mh);
        return;
    }

    /* Register the image */
    image_list_append(&shared_image_list, (uintptr_t) mh, info.dli_fname);
}

/**
 * @internal
 *
 * Write a binary image frame
 *
 * @param file Output file
 * @param name binary image path (or name).
 * @param image_base Mach-O image base.
 */
static void process_binary_image (const char *name, const void *header,
                                    struct uuid_command *out_uuid, uintptr_t *out_baseaddr) {
    uint32_t ncmds;
    const struct mach_header *header32 = (const struct mach_header *) header;
    const struct mach_header_64 *header64 = (const struct mach_header_64 *) header;

    struct load_command *cmd;

    /* Check for 32-bit/64-bit header and extract required values */
    switch (header32->magic) {
            /* 32-bit */
        case MH_MAGIC:
        case MH_CIGAM:
            ncmds = header32->ncmds;
            cmd = (struct load_command *) (header32 + 1);
            break;

            /* 64-bit */
        case MH_MAGIC_64:
        case MH_CIGAM_64:
            ncmds = header64->ncmds;
            cmd = (struct load_command *) (header64 + 1);
            break;

        default:
            NSLog(@"Invalid Mach-O header magic value: %x", header32->magic);
            return;
    }

    /* Compute the image size and search for a UUID */
    struct uuid_command *uuid = NULL;

    for (uint32_t i = 0; cmd != NULL && i < ncmds; i++) {
        /* DWARF dSYM UUID */
        if (cmd->cmd == LC_UUID && cmd->cmdsize == sizeof(struct uuid_command))
            uuid = (struct uuid_command *) cmd;

        cmd = (struct load_command *) ((uint8_t *) cmd + cmd->cmdsize);
    }

    /* Base address */
    uintptr_t base_addr;
    base_addr = (uintptr_t) header;

    *out_baseaddr = base_addr;
    if(out_uuid && uuid)
        memcpy(out_uuid, uuid, sizeof(struct uuid_command));
}

+ (void)registerCallback {
    _dyld_register_func_for_add_image(image_add_callback);
}

+ (NSArray *)loadedImages {
    NSMutableArray *array = [NSMutableArray array];
    int i;
    struct uuid_command uuid = { 0 };
    uintptr_t baseaddr;
    char uuidstr[64] = { 0 };

    image_list_set_reading(&shared_image_list, true);

    bin_image_t *image = NULL;
    while ((image = image_list_next(&shared_image_list, image)) != NULL) {

        process_binary_image(image->name, (const void *) (image->header), &uuid, &baseaddr);

        for(i=0; i<16; i++) {
            sprintf(&uuidstr[2*i], "%02x", uuid.uuid[i]);
        }

        [array addObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[NSString stringWithCString:image->name encoding:NSASCIIStringEncoding], [NSString stringWithCString:uuidstr encoding:NSASCIIStringEncoding], [NSNumber numberWithUnsignedLong:(long unsigned) baseaddr], nil]
                                                     forKeys:[NSArray arrayWithObjects:@"name", @"uuid", @"baseaddr", nil]]];
    }

    return [NSArray arrayWithArray:array];
}

You have to call registerCallbacks somewhere so that it can properly return results.

Regards.