Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing arguments from stack in i386, ARM assembly

I'm working with some trampoline functions for use with higher-level calling in C/Objective-C, a slight twist on the way Apple does it.

If you're familiar at all with the way Objective-C IMP works, it's basically a function pointer where the first two arguments are the receiver of a message and the name of the message selector, such as void(*)(id obj, SEL sel, ...). More recent versions of the runtime allow method implementations to be synthesized at run time using C blocks, like void(^)(id obj, ...). These blocks don't have the selector; the runtime creates a trampoline that overwrites the selector with the receiver, the receiver with the block pointer, and then moves on executing it.

I want to do something vaguely similar which involves not having either of the first two arguments, so that the arguments to this block are the exact same as the arguments of the traditional method send, plus the block pointer for execution purposes, i.e., void(*)(Block *, ...). This requires only copying in the block pointer, and I suppose getting rid of an argument.

__a1a2_tramphead_argonly:
    popl %eax
    andl $0xFFFFFFF8, %eax
    subl $0x1000, %eax
    movl 4(%esp), %ecx // self -> ecx
    movl %ecx, 8(%esp) // ecx -> _cmd
    movl (%eax), %ecx // blockPtr -> ecx
    movl %ecx, 4(%esp) // ecx -> self
    jmp  *12(%ecx) // tail to block->invoke

Here's the assembly I have on ARM:

__a1a2_tramphead_argonly:
    // calculate the trampoline's index (512 entries, 8 bytes each)
#ifdef _ARM_ARCH_7
    // PC bias is only 4, no need to correct with 8-byte trampolines
    ubfx r1, r1, #3, #9
#else
    sub  r1, r1, #8               // correct PC bias
    lsl  r1, r1, #20
    lsr  r1, r1, #23
#endif

    // load block pointer from trampoline's data
    adr  r12, __a1a2_tramphead_argonly    // text page
    sub  r12, r12, #4096          // data page precedes text page
    ldr  r12, [r12, r1, LSL #3]   // load block pointer from data + index*8

    // shuffle parameters
    mov  r1, r0                   // _cmd = self
    mov  r0, r12                  // self = block pointer

    // tail call block->invoke
    ldr  pc, [r12, #12]

Similar code exists for the x86_64; the code above is thus far directly from Apple. For personal knowledge, I'm wondering where to start with excising an argument, so that the first argument (what used to be the reciever) is the block literal, the second is the first real argument, and so on.

I'm incredibly noobish at ASM, so any help is greatly appreciated. Everything I've tried has blown up in increasingly interesting ways. Thanks in advance.

like image 760
zwaldowski Avatar asked Apr 02 '12 04:04

zwaldowski


Video Answer


1 Answers

The iOS ABI effectively incorporates the AAPCS and only defines the differences, so you'll want to start with http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0042d/index.html first. Then read Apple's iOS ABI Function Call Guide (which I think you need a paid iOS Dev Center membership to access).

Summarizing the rules, to call an ObjC IMP:

  • self goes in R0
  • _cmd goes in R1
  • first int or pointer argument goes in R2
  • second int or pointer argument goes in R3
  • all further arguments go on the stack

So, if you're only looking at arguments with up to 2 params, none of them floating point/int64_t/struct, to remove the self and _cmd arguments is just a matter of shuffling R0-R4:

mov r0, r2
mov r1, r3

Or, to write a function that takes two params and crams the self and _cmd in before forwarding to an IMP, it's just this:

mov r3, r1
mov r2, r0
ldr r1, [address of _cmd]
ldr r0, [address of self]

In the case of Apple's block trampoline, what they're doing is transforming a call to [foo performBlockOnSelf:block] into, effectively, [block foo]. As you say, the block pointer ends up in r0 (the usual self position) and the target parameter foo ends up in r1 (the usual _cmd position). If blocks were really IMPs, of course, this would be nonsense, because foo isn't a SEL, but they're not, so it's not a problem.

From your statement "I want to do something vaguely similar which involves not having either of the first two arguments, so that the arguments to this block are the exact same as the arguments of the traditional method send," I'm not entirely clear which of two things you're trying to do:

  1. Define a "delegate" object (in C# terms), basically a block with its target baked in at construction time. In this case, you're going to want to look up both r0 (the block pointer) and r1 (the target) from some table of delegates, instead of just the block pointer. But you're not going to have any compiler help setting up that table--which means you can set it up and access it in pure C and it'll be just as convenient and building a custom assembly trampoline. (You could even do it through ObjC dictionaries, with some loss of performance that may not matter in practice.)

  2. Turn a regular message into a block, which involves getting everything stored so that when Apple's trampoline code tries to call the block it ends up with the traditional method send parameters instead of the block parameters. If this is your goal, it's simpler and much safer to just use a block wrapper around the message instead of trying to convert the messages into blocks, and I doubt there would be either efficiency or flexibility costs that matter.

like image 63
abarnert Avatar answered Nov 13 '22 11:11

abarnert