Why IMP Calls Crash on ARM64 iOS: Uncovering Variadic ABI Pitfalls
This article investigates a crash caused by calling IMP pointers on ARM64 iOS devices, explains how variadic function argument passing differs from the standard ABI, demonstrates the issue with test code and assembly analysis, and provides a solution by explicitly casting IMP to the correct function type.
Crash Background
While developing a frequently called data‑processing method with a switch, we cached the method's IMP (function pointer) to bypass Objective‑C runtime checks. The code ran fine in debug and enterprise builds, but caused a reproducible crash on a real device, as shown in the stack trace.
Quick Review
Key points for reading the following code
1. An Objective‑C method call is translated into a plain IMP C function call. 2. IMP is a regular C function pointer, and SEL identifies the method. 3. You can obtain an IMP pointer via instanceMethodForSelector: and invoke it directly, bypassing the runtime.
Objective‑C method calls are compiled to objc_msgSend(receiver, @selector(message:), arg), where @selector(message:) is a SEL value identifying the method.
The IMP pointer has the prototype id (*IMP)(id, SEL, ...). Using NSObject 's instanceMethodForSelector: you can retrieve the IMP and call it directly.
In Objective‑C, id is a pointer to any object, similar to void * . NSObject is the base class of all objects.
Initial Analysis
Findings
1. The root cause is not ARC. 2. The direct cause is that objc_retain receives a stack address instead of an object. 3. The crash is solved by casting the IMP pointer to a function pointer with the correct parameter list.
The crash occurs in objc_storeStrong. Changing the parameter type of process_blackhole to void * or id __unsafe_unretained prevents the crash, but this is unsafe under ARC.
Analyzing objc_storeStrong shows it retains the incoming object, stores it at the given location, and releases the previous object at that location.
Stack‑trace analysis reveals that objc_retain receives a stack address, leading to a memory‑access crash.
Stack Overflow reports a similar IMP‑method crash; the fix is to explicitly cast the IMP to the proper function type.
Enabling “Enable Strict Checking of objc_msgSend Calls” in Xcode defines OBJC_OLD_DISPATCH_PROTOTYPES, which changes the IMP prototype to id (*IMP)(id, SEL, ...). Disabling the check reverts to the traditional prototype.
Test Code
Test 1
We declared a function pointer matching the IMP type with a variadic last parameter. The code runs on the simulator but crashes on a real device at the entry of internalProcess:.
Assembly shows the call (*processIMP)(self, processSEL, value) and the corresponding instructions.
Test 2
We added a second argument to observe the behavior. The same crash occurs, and assembly shows both arguments being passed as stack addresses.
Test 3
We defined the function pointer with the exact signature of the target method. Both simulator and device execute correctly.
Problem Analysis and Conclusion
Key observations:
On ARM64, variadic arguments are passed on the stack, while fixed arguments use registers.
iOS diverges from the generic ARM64 ABI: fixed arguments follow the standard, but all variadic arguments are always stacked.
This mismatch causes the called method (which expects fixed arguments) to receive incorrect values, leading to a crash.
Therefore, when invoking an IMP pointer on iOS ARM64, the compiler treats the call as a variadic function and stacks the extra arguments, while the callee expects them in registers. The solution is to explicitly cast the IMP (or objc_msgSend) to the exact function type before calling.
The iOS ABI for variadic functions differs from the generic procedure call standard: after the usual allocation of fixed arguments, each variadic argument is assigned to an 8‑byte stack slot.
In summary, the crash originates from the ABI’s handling of variadic functions on ARM64 iOS. Explicitly casting IMP pointers to the correct signature avoids the issue and aligns with Xcode’s default “Enable Strict Checking of objc_msgSend Calls” setting.
Tencent TDS Service
TDS Service offers client and web front‑end developers and operators an intelligent low‑code platform, cross‑platform development framework, universal release platform, runtime container engine, monitoring and analysis platform, and a security‑privacy compliance suite.
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.
