5

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'
jscs
  • 63,694
  • 13
  • 151
  • 195
Alex Gray
  • 16,007
  • 9
  • 96
  • 118
  • 1
    you can't really know anything about block itself inside the block unless you can refer to it somehow. BTW, why you want to know these information? – Bryan Chen Jul 02 '13 at 04:47
  • As blocks APIs proliferate.. it is often hard to tell the caller of the block, etc.. The compiler allows mismatched signatures... incorrect numbers of arguments, and multiple methods, with the same name, but different block types, etc. = to co-exist without complaint... It would be nice to know, sometimes, what is ACTUALLY happening... not just what "I think" is happening. – Alex Gray Jul 02 '13 at 07:00
  • 1
    you can check the type of the block before call it, but you can't do much inside the block because it is already called possibly with incorrect parameters – Bryan Chen Jul 02 '13 at 08:10
  • 1
    The Block does pass itself in, as you can see, in the first argument, à la `self`, but the _name_ for that argument doesn't exist in your code. There's no way to refer to it. I think the best you can do is some kind of wrapper around each Block invocation. – jscs Jul 02 '13 at 20:05
  • 1
    "Now, inside a block.. you can still call _cmd and get a vague description of the current "context".. ie. __30-[RoomController awakeFromNib]_block_invoke123RoomController" No you can't. That is not from `_cmd`. – newacct Jul 03 '13 at 04:11
  • You could be sneaky and evil and class pose CTBlockLiteral (seeing as it is very close to being an `objc_object`). Not the best solution, not compatible with the modern ABI, but still viable. – CodaFi Jul 03 '13 at 05:44

1 Answers1

5

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!

Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201