Removing arguments from stack in i386, ARM assembly

I work with some trampoline functions for use with higher-level calling in C / Objective-C, a little twist on the way Apple does this .

If you are familiar with how Objective-C IMP works, it is basically a pointer to a function where the first two arguments are the receiver of the message and the name of the message selector, for example void(*)(id obj, SEL sel, ...) , More later versions of executable implementations allow synthesizing implementation methods at runtime using C blocks, such as void(^)(id obj, ...) . These blocks do not have a selector; the runtime creates a trampoline that overwrites the selector with the receiver, the receiver with the block pointer, and then executes it.

I want to do something vaguely similar, which does not contain either of the first two arguments, so the arguments of this block are exactly the same as the arguments of the traditional send method, plus a block pointer for execution purposes, i.e., void(*)(Block *, ...) . It only requires copying in the block pointer, and I suppose getting rid of the 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 build on my ARM:

 __a1a2_tramphead_argonly: // calculate the trampoline 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 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 x86_64; the above code is so far directly from Apple. For personal knowledge, I wonder where to start by tempting an argument, so the first argument (what used to be a receiver) is a block literal, the second is the first real argument, etc.

I am incredibly noobish at ASM, so any help is greatly appreciated. Everything I tried exploded in increasingly interesting ways. Thanks in advance.

+8
assembly x86 arm objective-c block
source share
1 answer

iOS ABI effectively enables AAPCS and only detects differences, so first you want to start with http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0042d/index.html . Then read the Apple iOS ABI Feature Calling Guide (which I think you need to pay membership in the iOS Developer Center for access).

To summarize the rules, call ObjC IMP:

  • self goes to R0
  • _cmd goes to R1
  • The first argument of an int or pointer is in R2
  • second argument of int or pointer is in R3
  • all further arguments go to the stack

So, if you are only looking at arguments with two parameters, not one of them is floating point / int 64_t / struct, to remove the arguments self and _cmd, it's just shuffling R0-R4:

 mov r0, r2 mov r1, r3 

Or, to write a function that takes two parameters and writes self and _cmd before forwarding to IMP, it's simple:

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

In the case of the Apple block trampoline, what they do turns the call [foo performBlockOnSelf: block] into, effectively, [block foo]. As you say, the block pointer ends with r0 (normal native position), and the target parameter foo ends with r1 (normal position _cmd). If the blocks were really IMPs, of course, that would be stupid, because foo is not SEL, but it is not, so this is not a problem.

From your statement, β€œI want to do something vaguely similar, which implies the absence of one of the first two arguments, so the arguments of this block are exactly the same as the arguments of the traditional send method,” I’m not completely clear which of the two things you are trying to do :

  • Define a delegate object (in C # terms), basically a block with a target value baked at build time. In this case, you will need to search for both r0 (block pointer) and r1 (target) from some delegate table, and not just the block pointer. But you will not have any help from the compiler creating this table, which means that you can configure it and access it in pure C, and it will be as convenient as creating a custom assembly trampoline. (You could even do this through ObjC dictionaries, with some performance loss, which might not matter in practice.)

  • Turn the regular message into a block that includes everything that is stored, so when the Apple trampoline code tries to call a block, it ends with the traditional method of sending parameters instead of block parameters. If this is your goal, it’s easier and safer to just use the shell of the block around the message instead of trying to convert the messages into blocks, and I doubt it will matter the effectiveness or flexibility that matters.

+2
source share

All Articles