How to Hook objc_msgSend for Precise iOS Method Timing with Assembly
This article explains how to accurately measure the execution time of Objective‑C methods on iOS by implementing a custom objc_msgSend hook in ARM64 assembly, replacing the original implementation with fishhook, and visualizing the results using Chrome tracing, offering a low‑cost, non‑intrusive performance profiling solution.
Preface
Measuring the execution time of functions or methods is a common challenge for developers. The naive approach works for a few calls, but becomes impractical when profiling thousands of functions. This article presents a solution for iOS based on hooking objc_msgSend at the assembly level.
Background
Huolala's mobile business lines run strict performance tests before each release, including a startup test that uses the open‑source tidevice tool (similar to libimobiledevice). The current pipeline has several pain points:
Hard to locate the responsible component when performance issues arise.
Performance testing is at the end of the pipeline, causing delays.
Long analysis time makes the process unsustainable.
Xcode Instruments are powerful but the results are not persistent.
The goal is a low‑cost, non‑intrusive, high‑performance, visualizable solution.
Exploration
We reviewed existing solutions such as static instrumentation, Messier, Xcode Instruments, and objc_msgSend hooking. After comparing their pros and cons, we chose the objc_msgSend hook for its low intrusion, high performance, and visualizable output.
Comparison Table
Solution
Advantages
Disadvantages
Static Instrumentation
Can instrument entry and exit of every function
Increases binary size, cannot instrument closed‑source libraries, high integration cost
Messier
Visual analysis with timeline
Depends on third‑party maintenance, some iOS versions unsupported
Xcode Instruments
Powerful, supports multithreaded analysis
High learning curve, results are not persistent objc_msgSend Hook
Low intrusion, high performance, visualizable, supports timeline
Does not work on simulators, only for Objective‑C methods
Core Principles
objc_msgSendis the universal entry point for all Objective‑C method calls. It is implemented in assembly for performance and to handle variable argument lists. To replace it safely, we must preserve the register state before and after the original call.
ARM Assembly Basics
ARM64 provides 31 general‑purpose 64‑bit registers ( x0 ‑ x30) and a stack pointer ( sp). Registers x0 ‑ x7 are used for argument passing, fp ( x29) is the frame pointer, and lr ( x30) holds the return address. The br and ret instructions perform unconditional jumps.
Implementing a Custom objc_msgSend
The custom implementation follows three steps:
Save all registers ( GDN_STORE_REGISTERS macro).
Call a pre‑hook function to record the start time and method information.
Invoke the original objc_msgSend and then call a post‑hook to compute the elapsed time and restore registers.
.macro GDN_STORE_REGISTERS
stp fp, lr, [sp, #-0x10]!
mov fp, sp
sub sp, sp, #0x80
// Save x0‑x7, x19‑x28, etc.
.endm
.macro GDN_LOAD_REGISTERS
ldp x0, x1, [sp], #0x10
ldp x2, x3, [sp], #0x10
// Restore other registers
ldp fp, lr, [sp], #0x10
ret
.endm
.globl _gdn_objc_msgSend
_gdn_objc_msgSend:
GDN_STORE_REGISTERS
bl _needs_profiler
cbnz x0, GDN_NEEDS_PROFILER
GDN_LOAD_REGISTERS
// Call original objc_msgSend
bl _origin_objc_msgSend
GDN_STORE_REGISTERS
bl _post_objc_msgSend
GDN_LOAD_REGISTERS
retReplacing the Original objc_msgSend with Fishhook
We use Facebook’s fishhook library to rebind the symbol at runtime. The process iterates over all loaded Mach‑O images, locates the LC_SYMTAB and LC_DYSYMTAB commands, builds a list of symbols, and replaces the address of objc_msgSend with our custom implementation.
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
rebind_symbols((struct rebinding[1]){{"objc_msgSend", (void *)gdn_objc_msgSend, (void **)&origin_objc_msgSend}}, 1);Choosing the Right Hook Timing
To minimize the blind spot, the hook should be installed as early as possible. Placing the code in a dynamic library whose +load method is the first to run (by ordering the library first in the target’s link phase) ensures the replacement occurs before most other code executes.
+ (void)load {
// Replace original objc_msgSend with custom implementation
}Usage
1. Add the dependency via CocoaPods:
pod 'Guldan'2. Import the tracer and start/stop measurement around the code you want to profile:
#import "GDNOCMethodTracer.h"
[GDNOCMethodTracer start];
// ... code to profile ...
[GDNOCMethodTracer stop];3. Retrieve the recorded data:
[GDNOCMethodTracer handleRecordsWithComplete:^(NSArray<NSString *> * _Nonnull filePaths) {
// Process trace files
}];4. Export the trace files from the app’s container (Xcode → Devices → Download Container) and open them in Chrome’s chrome://tracing UI for visual analysis.
Result Demonstration
Desktop trace view:
Mobile trace view:
Conclusion and Outlook
We identified the performance‑profiling pain points in Huolala’s iOS apps, evaluated existing tools, and built a custom objc_msgSend hook solution that is low‑cost, non‑intrusive, and provides visualizable timing data. Future work includes:
Simplifying trace file extraction.
Adding real‑time monitoring.
Providing data analysis dashboards.
Supporting black‑listing of methods.
Extending the framework with more features.
References
[1] Shi Bin. “ARM Assembly Language and C/C++ Mixed Programming Methods.” *Electronic Measurement Technology*, 2006.
[2] Wang Yingjun et al. “Implementation of ARM Assembly and C Mixed Programming.” *Science & Technology Information*, 2010.
[3] Liu Feng et al. *ARM Assembly Language*. University of Electronic Science and Technology of China Press, 2010.
[4] Wen Quangang. *Assembly Language Programming Based on ARM Architecture*. Beijing University of Aeronautics and Astronautics Press, 2007.
[5] “Assembly Language Tutorial – Ruan Yifeng’s Blog.”
[6] https://github.com/DavidGoldman/InspectiveC
[7] https://github.com/ming1016/GCDFetchFeed
[8] https://opensource.apple.com/source/objc4/objc4-723/runtime/
[9] https://github.com/facebook/fishhook
[10] Dai Ming. “How to Optimize and Monitor App Startup Speed.”
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
