Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making super real by by Alexander Gusev

Making super real by by Alexander Gusev

Implementing a proxy object for invoking superclass method implementations of any Objective-C object.
https://github.com/sanekgusev/SGVSuperMessagingProxy

Original: https://speakerdeck.com/sanekgusev/making-super-real

CocoaHeadsNL

March 18, 2015
Tweet

More Decks by CocoaHeadsNL

Other Decks in Programming

Transcript

  1. What *is* super • A mere keyword • Not a

    real object • A way to call superclass' implementation of a certain method
  2. …isn't quite what you get @import ObjectiveC.runtime; struct objc_super super;

    super.receiver = self; super.super_class = [SuperClass class]; id result = (id(*)(struct objc_super*, SEL, id)objc_msgSendSuper) (&super, @selector(resultWithArg:), arg);
  3. Define “real” • An actual Objective-C object • Can be

    used with any Objective-C object • Responds to all selectors that original object's superclass instances respond to • Keeps reference to the original object
  4. Public interface @interface SGVSuper : ??? + (id)superWithObject:(id)object ancestorClass: (Class

    __unsafe_unretained)ancestor; + (id)superWithObject:(id)object; @end
  5. NSProxy • 2nd most popular root class in Foundation •

    Fewer methods than in NSObject • Memory-management is covered • Only useful for subclassing • Can be used for message forwarding
  6. Message forwarding path # What Method Level 1 Dynamic method

    resolution + resolveInstanceMethod: Class 2 Fast forwarding - forwardingTargetForSelector: Instance 3 Invocation forwarding - methodSignatureForSelector: - forwardInvocation: Instance 4 - doesNotRecognizeSelector: Instance
  7. Dynamic method resolution • First step in the forwarding chain

    • Defined at class level • Chance to add a method for the received selector at runtime • Message send is retried upon success
  8. Method adding checklist What's needed What it is Its type

    Class Class Method selector SEL Method implementation Function pointer IMP Method signature Type encoding string const char *
  9. Real-world example static NSString *ultimateQuestionIMP(id self, SEL selector, NSUInteger answer)

    { return nil; // FIXME: implement } NSString *typeEncoding = [NSString stringWithFormat:@"%s%s%s%s", @encode(NSString *), @encode(id), @encode(SEL), @encode(NSUInteger)]; BOOL methodAdded = class_addMethod([Earth class], @selector(questionForAnswer:), ultimateQuestionIMP, [typeEncoding UTF8String]);
  10. Method signature • C string with encoded types for return

    value and arguments • Encodings are implementation detail • Create by concatenating results of @encode directive • Get existing from method_getTypeEncoding()
  11. Can we add some methods yet? ✓ Class ✓ Method

    selector Have Still missing × Method signature × Method implementation
  12. Minor problem • Method signature available from original object's class

    • Original object is accessible through proxy instance • Method resolution is on class level ➡ Need to “encode” original object's class into proxy's class
  13. Subclass the proxy at runtime! • Dynamically subclass the proxy

    class • Embed original object's class name as part of proxy's subclass name • Alloc/init proxy subclass During proxy creation During method resolution • Retrieve original object's class from proxy subclass name • Obtain method signature through original object's class
  14. Subclassing in progress… NSString *proxyClassName = [NSStringFromClass([SGVSuper class]) stringByAppendingFormat:@"_%@", NSStringFromClass(objectClass)];

    Class __unsafe_unretained proxySubclass = objc_allocateClassPair([SGVSuper class], [proxyClassName UTF8String], 0); if (proxySubclass) { return proxySubclass; } return objc_lookUpClass([proxyClassName UTF8String]);
  15. What's an IMP? • A C function • Receives self

    as first argument and selector as second • Remaining arguments and return value should match method signature
  16. Mystical IMP Not your usual IMP SGVSuper objc_super selector arg

    0 arg 1 arg N … objc_msgSendSuper objc_super SGVSuper calls
  17. Not your usual IMP • Receives proxy instance as self

    • Receives selector and all other arguments • Should get objc_super from self • Should call objc_msgSendSuper() or objc_msgSendSuper_stret() • Should pass all other arguments verbatim • Should return result
  18. stret? • objc_msgSend(), objc_msgSend_stret(), objc_msgSend_fpret(), objc_msgSend_fpret2(), ... • objc_msgSendSuper(), objc_msgSendSuper_stret().

    • different CPUs — different rules for returning results • result *usually* returned in some register • registers have fixed size • structures can have arbitrary size ➡ large structures won't fit into registers
  19. ABI • Application Binary Interface • How to call functions,

    pass arguments, return values • Specific to platform • Available as crazy long PDF • Interesting read
  20. arm64 ABI • First 8 arguments are passed in registers

    x0-x7 • Remaining arguments are pushed onto the stack • Register x8 is reserved for returning large values • No stret dispatch
  21. How function-calling works • Caller puts arguments into registers and/or

    pushes onto the stack • Call == push next instruction address + unconditional jump • Control is transferred to called function • Callee retrieves arguments from registers and/or stack • Callee does something useful • Return == pop return address from stack + unconditional jump
  22. $x0 = $x0->_super Inside the IMP x0: SGVSuper objc_super x1

    stack x2 x7 … objc_msgSendSuper call Not so mystical IMP
  23. Inside the IMP • All arguments are already in their

    correct locations • Almost all • Replace proxy instance address in x0 with the address of objc_super ivar • objc_super is at a fixed offset from self • Offset can be retrieved from Objective-C runtime • Tail call to objc_msgSendSuper()
  24. Tail call? • Possible to avoid call if it's the

    last instruction and return value of the called function is also the return value of the current one • Call → jump • Stack frame is reused • Control will be returned directly to the function that called the current function • Current function symbol will be missing from stack trace • Clang does this all the time
  25. Just show me the code ptrdiff_t SuperOffset = ivar_getOffset( class_getInstanceVariable([SGVSuper

    class], "_super")); __attribute__((__naked__)) static void SGVMsgSendSuperTrampoline(void) { asm volatile ("adrp x9, _SuperOffset@PAGE\n\t" "add x9, x9, _SuperOffset@PAGEOFF\n\t" "ldr x9, [x9]\n\t" "add x0, x0, x9\n\t" "b _objc_msgSendSuper\n\t" : : : "x0", "x9"); }
  26. Loading _super ivar offset ptrdiff_t SuperOffset = ivar_getOffset( class_getInstanceVariable([SGVSuper class],

    "_super")); __attribute__((__naked__)) static void SGVMsgSendSuperTrampoline(void) { asm volatile ("adrp x9, _SuperOffset@PAGE\n\t" "add x9, x9, _SuperOffset@PAGEOFF\n\t" "ldr x9, [x9]\n\t" "add x0, x0, x9\n\t" "b _objc_msgSendSuper\n\t" : : : "x0", "x9"); }
  27. Calculating _super address ptrdiff_t SuperOffset = ivar_getOffset( class_getInstanceVariable([SGVSuper class], "_super"));

    __attribute__((__naked__)) static void SGVMsgSendSuperTrampoline(void) { asm volatile ("adrp x9, _SuperOffset@PAGE\n\t" "add x9, x9, _SuperOffset@PAGEOFF\n\t" "ldr x9, [x9]\n\t" "add x0, x0, x9\n\t" "b _objc_msgSendSuper\n\t" : : : "x0", "x9"); }
  28. Tail call ptrdiff_t SuperOffset = ivar_getOffset( class_getInstanceVariable([SGVSuper class], "_super")); __attribute__((__naked__))

    static void SGVMsgSendSuperTrampoline(void) { asm volatile ("adrp x9, _SuperOffset@PAGE\n\t" "add x9, x9, _SuperOffset@PAGEOFF\n\t" "ldr x9, [x9]\n\t" "add x0, x0, x9\n\t" "b _objc_msgSendSuper\n\t" : : : "x0", "x9"); }
  29. Even more assembly? $ pod try SGVSuperMessagingProxy $ git clone

    https://github.com/sanekgusev/SGVSuperMessagingProxy.git