Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Introspect the current block context, à la _cmd inside a method

For me, Objective-C's ability to react, describe, and mess-with its surroundings is where it's at. This starts, at a fundamental level, with an unwavering ability to refer to _cmd, at any point, and get the current SEL. From there, it is up to you what NSInvocation incantations or runtime chicanery you choose to partake in.

Now, inside a block, you can still call _cmd and get a vague description of the current "context", i.e.

__30-[RoomController awakeFromNib]_block_invoke123RoomController

Descriptive? Yes. Informative? Okay... But not so useful. How to do I get dynamic and accurate runtime info inside a block, specifically the calling signature, args, etc.?

I have found a useful little method to "describe" a block ahead of time that gives a good example of the type of information I am hoping to garner INSIDE the block.

typedef void(^blockHead)(NSString*);
blockHead v = ^(NSString*sandy) {  NSLog(@"damnDog",nil); };
Log([v blockDescription]);

[v blockDescription] = <NSMethodSignature: 0x7fd6fabc44d0>
    number of arguments = 2
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
    type encoding (@) '@?'
    flags {isObject, isBlock}
    modifiers {}
    frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
    memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
    type encoding (@) '@"NSString"'
    flags {isObject}
    modifiers {}
    frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
    memory {offset = 0, size = 8}
        class 'NSString'
like image 682
Alex Gray Avatar asked Jul 02 '13 03:07

Alex Gray


1 Answers

If you dig deep enough, it is indeed possible with some target-specific assembly.

There are three main architectures you will be running objective-c code on, which are:

  • x86: iOS Simulator, and ancient Macs
  • x86_64: Mac OSX
  • ARM: iOS Devices.

Using the lldb debugger, along with a lot of hacking, I have come up with the registers that are in use for each platform (for holding the block pointer):

  • x86: ecx / edi
  • x86_64: rcx / rdi
  • ARM: r0 / r4

On all platforms, the values appear to be in two separate registers, perhaps one from the calling point and one from the argument passed.

Using this information, I have made a few macros that will work with both GCC and Clang to get the values of said registers into a C variable:

#if TARGET_CPU_X86_64 
// OSX, the block pointer is in the register 'rcx'.
// The 'mov' instruction does not clobber the register,
// So we can simply (ab)use that here.
#define BLOCK_GET_SELF() ({ id __block_self_tmp; __asm__("mov %%rcx, %0" : "=r"(__block_self_tmp)); __block_self_tmp; })
#elif TARGET_CPU_X86 
// iOS Simulator, the block pointer is in the register 'ecx'.
// Same deal as with x86_64 code, except it's in a 32-bit register.
#define BLOCK_GET_SELF() ({ id __block_self_tmp; __asm__("mov %%ecx, %0" : "=r"(__block_self_tmp)); __block_self_tmp; })
#elif TARGET_CPU_ARM64
// iOS Device, ARM64 (iPhone 5S, iPad Mini 2, iPad Air).
// The block pointer is in the x0 register, and the x4 register.
// Similar code to the TARGET_CPU_ARM function.
#define BLOCK_GET_SELF() ({ id __block_self_tmp; __asm__("str x0, [%0]" :: "r"(&__block_self_tmp)); __block_self_tmp; })
#elif TARGET_CPU_ARM 
// iOS Device, the block pointer is in register 'r0'.
// The 'mov' (move) instruction clobbers the r0 register
// (which messes up the debugger) for whatever reason,
// so we use the 'str' (store) instruction instead.
#define BLOCK_GET_SELF() ({ id __block_self_tmp; __asm__("str r0, [%0]" :: "r"(&__block_self_tmp)); __block_self_tmp; })
#endif

void blockTest() {
    __block void *blockPtr = NULL;
    void (^myBlock)() = ^{
        id this = BLOCK_GET_SELF();

        printf("this is:\t\t0x%.8lx\n", (uintptr_t) this);
        printf("blockPtr is:\t0x%.8lx\n", (uintptr_t) blockPtr);
    };

    // example using dispatch
    blockPtr = (__bridge void *) myBlock;
    dispatch_async(dispatch_get_main_queue(), myBlock);
}

Output, iPhone 5 running iOS 7 Beta 2:

this is:        0x17e7c890
blockPtr is:    0x17e7c890

Feel free to let me know of any issues with this code, and I hope it helps you out!

like image 148
Richard J. Ross III Avatar answered Oct 09 '22 17:10

Richard J. Ross III