Mobile Development 11 min read

iOS Method Execution Time Monitoring: Hooking objc_msgSend with Fishhook and Assembly

This article explains how to implement method‑execution time monitoring on iOS by hooking objc_msgSend using fishhook and custom assembly, detailing the concepts of hooks, the dyld symbol‑rebinding process, and providing complete code examples for building the QiLagMonitor tool.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
iOS Method Execution Time Monitoring: Hooking objc_msgSend with Fishhook and Assembly

After studying a series of performance‑monitoring talks, the author summarizes practical iOS monitoring techniques and outlines a three‑part article series; this part focuses on the method‑timing module of the QiLagMonitor tool.

What is a hook? A hook replaces the original implementation of a method either at its entry point or before/after its execution. In iOS this can be achieved with the Objective‑C runtime Method Swizzle or with the open‑source fishhook library that rebinding symbols in the Mach‑O binary.

Why hooking objc_msgSend captures all method timings – every Objective‑C message is dispatched through objc_msgSend , so intercepting this function lets us record the duration of any method call.

Stage 1 – Preparing the rebinding infrastructure – two C structures are defined to describe a symbol and a linked list of rebinding entries, then the dynamic linker ( dyld ) is traversed to locate each image’s header, segment commands and symbol tables.

struct rebinding {
    const char *name;
    void *replacement;
    void **replaced;
};

struct rebindings_entry {
    struct rebinding *rebindings;
    size_t rebindings_nel;
    struct rebindings_entry *next;
};

The core function fish_rebind_symbols registers a callback for newly loaded images and iterates over existing images to apply the rebinding.

static int fish_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
    int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
    if (retval < 0) return retval;
    if (!_rebindings_head->next) {
        _dyld_register_func_for_add_image(_rebind_symbols_for_image);
    } else {
        uint32_t c = _dyld_image_count();
        for (uint32_t i = 0; i < c; i++) {
            _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
        }
    }
    return retval;
}

Within rebind_symbols_for_image the code locates the LC_SEGMENT_ARCH_DEPENDENT , LC_SYMTAB and LC_DYSYMTAB commands, builds pointers to the symbol and string tables, and then walks the lazy/non‑lazy symbol pointer sections to replace the target symbols.

static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
    // ... locate segment_command_t, symtab_command, dysymtab_command ...
    // ... iterate over sections and perform rebinding ...
}

Stage 2 – Hooking objc_msgSend at the assembly level – because objc_msgSend is written in ARM64 assembly, the author creates a naked function hook_objc_msgSend that saves registers, calls before_objc_msgSend , forwards to the original implementation, then calls after_objc_msgSend before restoring state and returning.

#define save() \
    __asm volatile ("stp x8, x9, [sp, #-16]!\n" \
                   "stp x6, x7, [sp, #-16]!\n" \
                   "stp x4, x5, [sp, #-16]!\n" \
                   "stp x2, x3, [sp, #-16]!\n" \
                   "stp x0, x1, [sp, #-16]!")

#define load() \
    __asm volatile ("ldp x0, x1, [sp], #16\n" \
                   "ldp x2, x3, [sp], #16\n" \
                   "ldp x4, x5, [sp], #16\n" \
                   "ldp x6, x7, [sp], #16\n" \
                   "ldp x8, x9, [sp], #16")

__attribute__((__naked__))
static void hook_objc_msgSend() {
    save();
    __asm volatile ("mov x2, lr\n");
    __asm volatile ("mov x3, x4\n");
    call(blr, &before_objc_msgSend);
    load();
    call(blr, orig_objc_msgSend);
    save();
    call(blr, &after_objc_msgSend);
    __asm volatile ("mov lr, x0\n");
    load();
    ret();
}

How to use the tool – import the QiLagMonitor library, include QiCallTrace.h in the target view controller, then wrap the code region with [QiCallTrace start]; … [QiCallTrace stop]; [QiCallTrace save]; . The current implementation only hooks Objective‑C methods; Swift methods are not supported.

Overall, the article provides a complete walkthrough from the theoretical concept of method hooking to a practical, low‑level implementation that can be integrated into iOS projects for fine‑grained performance analysis.

iOSRuntimeperformance-monitoringassemblyMethod HookingFishhookobjc_msgSend
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.